我们说我有一个班级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
?
感谢。
答案 0 :(得分:2)
默认情况下,如果您想要类型为T
的值,请使用类型为T
的数据成员。它简单而有效。
Position position;
通常,您希望通过值或引用返回此值。我倾向于按值返回基本类型的对象:
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)
{
}
并像往常一样通过引用返回:
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)
{
}
我们像以前一样通过引用返回:
Position const& getPosition() const
{
return *position;
}
请注意,构造函数接受引用而不是指针,因为它会阻止调用者传递nullptr
。我们当然可以通过指针,但如前所述,这表明调用者该值是可选的:
Floor(Position* p) :
position(p)
{
}
我们可能希望通过指针返回const:
Position const* getPosition() const
{
return position;
}
我认为就是这样。我花了很长时间写这个(可能是不必要的详细)答案比我想要的。希望它有所帮助。