Getter 和实例变量、引用、指针或对象?

Getter and instance variable, reference, pointer, or object?

假设我有一个 class 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;  
};

和一个class Floor:

class Floor {

 public:
   Floor(Position p) : position(p) { }
 private:
   Position position;
    };

如果我要添加一个 getter 比如 getPosition(void),它应该是什么 return? PositionPosition*Position&? 我应该将 Position position 还是 Position* position 作为实例变量?还是Position& position? 谢谢。

默认情况下,如果您想要 T 类型的值,请使用 T 类型的数据成员。简单高效。

Position position;

通常,您会希望 return 按值或对 const 的引用来 return this。我倾向于按值 return 基本类型的对象:

int getX() const
{
    return x;
}

和 class 对象通过引用 const:

Position const& getPosition() const
{
    return position;
}

复制成本高昂的对象通常会受益于通过引用 const returned。某些 class 对象的值可能比 return 更快,但您必须进行基准测试才能找到答案。


在不太常见的情况下,您希望允许调用者修改您的数据成员,您可以 return 通过引用:

Position& getPosition()
{
    return position;
}

但是,通常最好防止像这样直接访问 class 内部结构。它使您在将来可以更自由地更改 class 的实施细节。


如果出于某种原因需要动态分配值(例如,实际对象可能是运行时确定的多个派生类型之一),您可以使用 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))
{
}

请注意,您仍然可以 return 按值或引用 const:

Position const& getPosition() const
{
    return *position;
}

也就是只要position就不能包含nullptr。如果可以,那么您可能想要 return 一个指向 const:

的指针
Position const* getPosition() const
{
    return position.get();
}

这是现代 C++ 中原始指针的真正用法。从语义上讲,这向调用者传达值 returned 是 "optional" 并且可能不存在。你不应该 return 对 const std::unique_ptr<T> 的引用,因为你实际上可以修改它指向的值:

std::unique_ptr<Position> const& getPosition() const
{
    return position;
}

*v.getPosition() = Position(4, 2); // oops

此外,returnstd::unique_ptr 会再次暴露不必要的实现细节,您最好不要这样做。


多个对象也可以拥有同一个动态分配的对象。在这种情况下,您可以使用 std::shared_ptr:

std::shared_ptr<Position> position;

使用 std::make_shared 创建一个新值:

Floor() :
    position(std::make_shared<FunkyPosition>(4, 2))
{
}

或copy/move现有的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))
{
}

A 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)
{
}

和return照常引用const:

Position const& getPosition() const
{
    return position;
}

您的 class 的用户必须小心,因为引用对象的生命周期不受持有引用的 class 管理;它由他们管理:

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++ 软件设计的宝贵工具。

引用的唯一问题是您无法更改它们引用的内容。这意味着不能复制分配具有引用数据成员的 classes。如果您的 class 不需要可复制,这不是问题,但如果需要,您可以使用指针代替:

Position* position;

通过引用地址构造它:

Floor(Position& p) :
    position(&p)
{
}

而我们return像以前一样通过引用const:

Position const& getPosition() const
{
    return *position;
}

请注意,构造函数采用的是引用,而不是指针,因为它会阻止调用者传递 nullptr。我们 可以 当然可以通过指针获取,但是如前所述,这向调用者暗示该值是可选的:

Floor(Position* p) :
    position(p)
{
}

再一次,我们可能希望通过指向 const:

的指针 return
Position const* getPosition() const
{
    return position;
}

我想就是这样。我写这个(可能不必要地详细)答案的时间比我预期的要长得多。希望对你有帮助。