Предоставление ребенку доступа к родительскому члену по ссылке – это нормально?
-
20-08-2019 - |
Вопрос
Вопрос новичка в 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
и CGirlfriend
(и CBoyfriend
, возможно).На этом этапе, я думаю, вы понимаете, что такой уровень детализации бессмысленен перед лицом 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++...).
- Лично меня смущает, когда имена членов и переменных начинаются с заглавной буквы.Я видел это очень редко.Но это не так уж и важно, это всего лишь личный стиль.
- Есть известное правило(Ноль-Один-Бесконечность), в котором говорится, что когда у вас есть две вещи, вы, как правило, должны иметь возможность иметь произвольное количество этих вещей.Итак, если вы можете иметь двоих детей, почему бы не завести их много?Два кажутся произвольным выбором.Но в вашем случае у этого может быть веская причина — поэтому игнорируйте это, если в вашем случае это имеет смысл.