Предоставление ребенку доступа к члену родителя по ссылке - это нормально? - PullRequest
0 голосов
/ 29 января 2009

C ++ вопрос новичка. Пожалуйста, убедитесь, что я все делаю правильно.

У меня есть глобальный класс приложений, порождающий маленьких детей, и мне нужно предоставить детям доступ к некоторым средствам приложений. Поэтому я решил передать их детям по ссылке.

Я проверил идею, как показано ниже. Вроде нормально работает. Я просто хотел убедиться, что я не делаю что-то опасное. Могут ли быть какие-то подводные камни, которые я упустил из виду?

Папа создает детей и дает им ключи от машины:

#include <iostream>
using namespace std;

class CCarKeys
{
    public:
        CCarKeys(const string& Name) : _Name(Name) {}
        string _Name;
};

class CChild
{
    public:
        CChild(CCarKeys& CarKeys) : _Name("Child"), _CarKeys(CarKeys) {}
        string _Name;
        CCarKeys& _CarKeys;
        void TestHasKeys() {cout << "I got " << _CarKeys._Name << endl;}
};

class CDad
{
    public:
        CDad() : _Name("Dad"), _HondaCarKeys("Honda keys"), _ChevyCarKeys("Chevy keys") {}
        string _Name;
        CCarKeys _HondaCarKeys;
        CCarKeys _ChevyCarKeys;
        CChild *_Boy;
        CChild *_Girl;
        void MakeBoy() {_Boy= new CChild(_HondaCarKeys);}
        void MakeGirl() {_Girl= new CChild(_ChevyCarKeys);}
};

int main ()
{
    CDad Dad;

    Dad.MakeBoy();
    Dad.MakeGirl();
    Dad._Boy->TestHasKeys();
    Dad._Girl->TestHasKeys();
}

Ответы [ 4 ]

0 голосов
/ 29 января 2009

Это возможно, и в вашем коде это не приносит вреда. Но это опасно, потому что если вы копируете CDad, то ключи и указатели будут скопированы вместе. Объекты, на которые будут указывать указатели, и ссылка внутри этих объектов, тем не менее, останутся прежними. Если исходный объект CDad выходит из области видимости, ссылки в объектах, на которые ссылаются указатели, висят и больше не ссылаются на действительный объект.

Может быть, вы можете изменить время жизни: создайте ключи в куче, и дети как обычные члены в классе. Поэтому, если вы копируете папу, дети копируются, а ключи - нет. Я думаю, что ключи являются неизменяемыми, поэтому вы можете использовать один и тот же ключ среди нескольких детей.

Это приводит к другому вопросу: если ваши ключи достаточно малы (читай: не огромны) и неизменны (поэтому у вас нет аномалий обновления, если вы меняете один ключ, но не другие), рассмотрите возможность его создания не в куче тоже - так что они тоже автоматически копируются и передают их детям, когда им нужен ключ. Вы можете сделать их обычными указателями на детей, но я думаю, что это ужасно, потому что ребенок не содержит ключ *, а использует его. Таким образом, указатель / ссылка или параметр функции хорошо подходит, но не «реальный» элемент данных.

Если вы идете с общим ключом и ключами на куче, вы должны использовать умные указатели - потому что вы должны отслеживать всех детей и пап. Если последний ребенок / папа выходит из области видимости, вам придется снова удалить ключ. Для этого вы используете boost::shared_ptr:

class CCarKeys
{
    public:
        CCarKeys(const string& Name) : _Name(Name) {}
        string _Name;
};

class CChild
{
    public:
        CChild(boost::shared_ptr<CCarKeys> const& CarKeys) 
            : _Name("Child"), _CarKeys(CarKeys) {}
        string _Name;
        boost::shared_ptr<CCarKeys> _CarKeys;
        void TestHasKeys() {cout << "I got " << _CarKeys._Name << endl;}
};

class CDad
{
    public:
        // NOTE: null the kid pointers *if* you use raw pointers, so you can check whether
        // a boy or girl is present. Without nulling them explicitly, they have 
        // indeterminate values. Beware. shared_ptr's however will automatically
        // initialized to default "null" values
        CDad() : 
            _Name("Dad"), 
            _HondaCarKeys(new CCarKeys("Honda keys")), 
            _ChevyCarKeys(new CCarKeys("Chevy keys")) {}
        string _Name;

        boost::shared_ptr<CCarKeys> _HondaCarKeys;
        boost::shared_ptr<CCarKeys> _ChevyCarKeys;

        // also use shared_ptr for the kids. try to avoid raw pointers. 
        boost::shared_ptr<CChild> _Boy;
        boost::shared_otr<CChild> _Girl;

        void MakeBoy() {_Boy.reset(new CChild(_HondaCarKeys));}
        void MakeGirl() {_Girl.reset(new CChild(_ChevyCarKeys));}
};

// main can be used unchanged

Конечно, вы можете избежать всей этой сложности, просто сделав класс CDad недоступным для копирования. Затем вы можете использовать свое оригинальное решение, просто сделав так, чтобы дети использовали shared_ptr и сделали детей также не подлежащими копированию. В идеале, следует использовать не-совместно используемый указатель, такой как auto_ptr, но у auto_ptr также есть некоторые подводные камни, которых все разделяет shared_ptr:

class CCarKeys
{
    public:
        CCarKeys(const string& Name) : _Name(Name) {}
        string _Name;
};

class CChild
{
    public:
        CChild (CCarKeys& CarKeys) 
            : _Name("Child"), _CarKeys(CarKeys) {}
        string _Name;
        CCarKeys &_CarKeys;
        void TestHasKeys() {cout << "I got " << _CarKeys._Name << endl;}
    private:
        CChild(CChild const&); // non-copyable
        CChild & operator=(CChild const&); // non-assignable
};

class CDad
{
    public:
        CDad() : 
            _Name("Dad"), 
            _HondaCarKeys("Honda keys"), 
            _ChevyCarKeys("Chevy keys") {}
        string _Name;

        CCarKeys _HondaCarKeys;
        CCarKeys _ChevyCarKeys;

        // also use shared_ptr for the kids. try to avoid raw pointers. 
        boost::shared_ptr<CChild> _Boy;
        boost::shared_otr<CChild> _Girl;

        void MakeBoy() {_Boy.reset(new CChild(_HondaCarKeys));}
        void MakeGirl() {_Girl.reset(new CChild(_ChevyCarKeys));}
private:
    CDad(CDad const&); // non-copyable
    CDad & operator=(CDad const&); // non-assignable
};

Если бы мне пришлось реализовать такую ​​иерархию классов, я бы выбрал это решение или просто отбросил ключи как члены и передавал / создавал их при необходимости детям. Некоторые другие замечания о вашем коде:

  • Лучше отбросьте "_" у членов или поставьте их в конце или используйте другие обозначения. Имя, которое начинается со знака подчеркивания и сопровождается заглавной буквой, зарезервировано реализациями C ++ (компилятор, C ++ std lib ...).
  • Лично меня смущает, что имена членов и переменные начинаются с заглавной буквы. Я видел это очень редко. Но это не очень важно, это просто личный стиль.
  • Существует известное правило ( Zero-One-Infinity ), которое гласит, что когда вы получаете две вещи чего-то, вы, как правило, должны иметь возможность иметь множество таких вещей. Итак, если вы можете иметь двоих детей - почему бы не иметь их много? Два кажется произвольным выбором. Но это может иметь вескую причину в вашем случае - поэтому игнорируйте это, когда в вашем случае это имеет смысл.
0 голосов
/ 29 января 2009

Передача по ссылке - это то же самое, что и передача по указателю, за исключением семантики и того факта, что вы не можете ничего сделать с указателем при передаче по ссылке.

Ваш код в порядке.

0 голосов
/ 29 января 2009

В вашем конкретном случае ключи от машины, скорее всего, не будут выданы навсегда, а скорее запрошены по мере необходимости и выдаются на основе запроса. Так что больше

class Dad
{
   /** may return NULL if no keys granted */
   CarKeys *requestKeys(CChild forChild);
}

В более общем случае класса основного приложения и дочерних элементов, как насчет создания объекта данных, совместно используемого приложением и дочерними объектами в main (), и передачи ссылок всем.

0 голосов
/ 29 января 2009

Хорошо выглядит для меня (если ключи - это все, что им нужно). Они могут нуждаться в некоторых других услугах от папы, которые запрашиваются позже, например:

Wallet += MyDad.GasMoney(REQUEST_MAX_AND_PROMISE_TO_BE_HOME_BY_10PM) ;

Но у них нет ссылки на папу, поэтому они не смогут этого сделать. Поэтому я бы хотел, чтобы конструктор CChild тоже взял ссылку this.

class ICashProvider {
public:
  virtual money Request(IPerson,CashRequestFlags) ;
};

class IChaffeur {
public:
  virtual void Drive(IPerson[]) ;
};

и т.д.

И тогда CChild конструктор должен будет принять ICashProvider и IChaffeur, как и CWife и CGirlfriend (и, возможно, CBoyfriend). На данный момент, я думаю, вы могли бы понять, что этот уровень детализации бессмысленен перед лицом обязанностей Dad, и вы просто даете всем this и получаете Dad аутентификации запросов, заставляя абонентов отправлять свои this на некоторых методах, чтобы у вас не было Dad выполнения инцеста или смены подгузника CWife.

...