Предоставление ребенку доступа к родительскому члену по ссылке – это нормально?

StackOverflow https://stackoverflow.com/questions/491972

Вопрос

Вопрос новичка в 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();
}
Это было полезно?

Решение

На мой взгляд, выглядит хорошо (если ключи — это все, что им нужно).Им могут понадобиться некоторые другие услуги от папы, которые они запросят позже, например:

Wallet += MyDad.GasMoney(REQUEST_MAX_AND_PROMISE_TO_BE_HOME_BY_10PM) ;

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

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

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

и т. д.

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

Другие советы

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

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

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

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

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

Это возможно, и в вашем коде это не причинит вреда.Но это опасно, потому что если скопировать 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 некопируемым.Затем вы можете использовать свое оригинальное решение, просто заставив детей использовать общий_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++...).
  • Лично меня смущает, когда имена членов и переменных начинаются с заглавной буквы.Я видел это очень редко.Но это не так уж и важно, это всего лишь личный стиль.
  • Есть известное правило(Ноль-Один-Бесконечность), в котором говорится, что когда у вас есть две вещи, вы, как правило, должны иметь возможность иметь произвольное количество этих вещей.Итак, если вы можете иметь двоих детей, почему бы не завести их много?Два кажутся произвольным выбором.Но в вашем случае у этого может быть веская причина — поэтому игнорируйте это, если в вашем случае это имеет смысл.
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top