Допустим, у меня есть класс Position
:
class Position{
public:
Position(int x, int y) : x(x), y(y) {}
int getX(void) { return x; }
int getY(void) { return y; }
private:
int x;
int y;
};
и класс Floor
:
class Floor {
public:
Floor(Position p) : position(p) { }
private:
Position position;
};
Если бы я добавить геттер, как getPosition(void)
что он должен вернуть?
Position
? Position*
? Position&
?
И я должен иметь Position position
или же Position* position
как переменная экземпляра? Или же Position& position
?
Благодарю.
По умолчанию, если вы хотите значение типа T
, используйте элемент данных типа T
, Это просто и эффективно.
Position position;
Обычно вы хотите вернуть это по значению или по ссылке на const. Я склонен возвращать объекты фундаментального типа по значению:
int getX() const
{
return x;
}
и класс объектов по ссылке на const:
Position const& getPosition() const
{
return position;
}
Объекты, которые являются дорогостоящими для копирования, часто получают выгоду от возврата по ссылке на const. Некоторые объекты класса могут быстрее возвращаться по значению, но вам нужно будет сравнить их, чтобы выяснить это.
В менее распространенном случае, когда вы хотите разрешить вызывающему абоненту изменять ваш элемент данных, вы можете вернуться по ссылке:
Position& getPosition()
{
return position;
}
Тем не менее, обычно лучше предотвратить прямой доступ к внутренним классам, подобным этому. Это дает вам больше свободы для изменения деталей реализации вашего класса в будущем.
Если по какой-то причине вам нужно динамически распределить значение (например, фактический объект может быть одним из нескольких производных типов, определенных во время выполнения), вы можете использовать std::unique_ptr
тип:
std::unique_ptr<Position> position;
Создать новое значение, используя std::make_unique
:
Floor() :
position(std::make_unique<FunkyPosition>(4, 2))
{
}
Или переехать в существующий std::unique_ptr
:
Floor(std::unique_ptr<Position> p) :
position(std::move(p))
{
}
Обратите внимание, что вы все равно можете вернуть по значению или по ссылке на const:
Position const& getPosition() const
{
return *position;
}
То есть пока position
не может содержать nullptr
, Если это возможно, то вы можете вернуть указатель на const:
Position const* getPosition() const
{
return position.get();
}
Это подлинное использование необработанных указателей в современном C ++. Семантически это сообщает вызывающей стороне, что возвращаемое значение является «необязательным» и может не существовать. Вы не должны возвращать ссылку на const std::unique_ptr<T>
потому что вы можете изменить значение, на которое оно указывает:
std::unique_ptr<Position> const& getPosition() const
{
return position;
}
*v.getPosition() = Position(4, 2); // oops
Кроме того, возвращая std::unique_ptr
будет снова выставлять ненужные детали реализации, что вы предпочитаете не делать.
Кроме того, несколько объектов могут владеть одним и тем же динамически размещенным объектом. В этом случае вы можете использовать std::shared_ptr
:
std::shared_ptr<Position> position;
Создать новое значение, используя std::make_shared
:
Floor() :
position(std::make_shared<FunkyPosition>(4, 2))
{
}
Или скопировать / переместить в существующий std::shared_ptr
:
Floor(std::shared_ptr<Position> p) :
position(std::move(p))
{
}
Или переехать в существующий std::unique_ptr
:
Floor(std::unique_ptr<Position> p) :
position(std::move(p))
{
}
Только один раз все std::shared_ptr
Указание на объект был уничтожен, сам объект уничтожен. Это гораздо тяжелее, чем обертка std::unique_ptr
так что используйте экономно.
В случае, когда ваш объект ссылается на объект, который ему не принадлежит, у вас есть несколько вариантов.
Если указанный объект хранится в std::shared_ptr
, вы можете использовать элемент данных типа std::weak_ptr
:
std::weak_ptr<Position> position;
Построить его из существующего std::shared_ptr
:
Floor(std::shared_ptr<Position> p) :
position(std::move(p))
{
}
Или даже другой std::weak_ptr
:
Floor(std::weak_ptr<Position> p) :
position(std::move(p))
{
}
std::weak_ptr
не владеет объектом, на который он ссылается, поэтому объект может или не может быть уничтожен, в зависимости от того, все ли std::shared_ptr
владельцы были уничтожены. Вы должны замок std::weak_ptr
чтобы получить доступ к объекту:
auto shared = weak.lock();
if (shared) // check the object hasn't been destroyed
{
…
}
Это супер безопасно, потому что вы не можете случайно разыменовать указатель на удаленный объект.
Но что делать, если указанный объект не хранится в std::shared_ptr
? Что делать, если он хранится в std::unique_ptr
или даже не хранится в умном указателе? В этом случае вы можете сохранить ссылку или ссылку на const:
Position& position;
Построить его из другой ссылки:
Floor(Position& p) :
position(p)
{
}
И вернитесь по ссылке на const как обычно:
Position const& getPosition() const
{
return position;
}
Пользователи вашего класса должны быть осторожны, поскольку время жизни ссылочного объекта не управляется классом, содержащим ссылку; им управляют:
Position some_position;
Floor some_floor(some_position);
Это означает, что если объект, на который ссылаются, уничтожается перед объектом, на который он ссылается, то у вас есть свисающая ссылка, которую нельзя использовать:
auto some_position = std::make_unique<Position>(4, 2);
Floor some_floor(*some_position);
some_position = nullptr; // careful...
auto p = some_floor.getPosition(); // bad!
Пока этой ситуации тщательно избегают, хранение ссылки в качестве члена данных является совершенно допустимым. Фактически, это бесценный инструмент для эффективного проектирования программного обеспечения на C ++.
Единственная проблема со ссылками в том, что вы не можете изменить то, что они ссылаются. Это означает, что классы с элементами справочных данных не могут быть назначены для копирования. Это не проблема, если ваш класс не должен быть копируемым, но если это так, вы можете использовать вместо него указатель:
Position* position;
Построить его, взяв адрес ссылки:
Floor(Position& p) :
position(&p)
{
}
И мы возвращаемся по ссылке на const, как и раньше:
Position const& getPosition() const
{
return *position;
}
Обратите внимание, что конструктор принимает ссылку, а не указатель, потому что он не позволяет вызывающим абонентам передавать nullptr
, Мы мог конечно, взять по указателю, но, как упоминалось ранее, это говорит вызывающей стороне, что значение является необязательным:
Floor(Position* p) :
position(p)
{
}
И снова мы можем захотеть вернуться указателем на const:
Position const* getPosition() const
{
return position;
}
И я думаю, что это так. Я потратил гораздо больше времени на написание этого (возможно, излишне подробного) ответа, чем хотел. Надеюсь, поможет.
Других решений пока нет …