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

时间:2016-03-14 13:09:44

标签: c++ pointers reference getter instance-variables

我们说我有一个班级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)这样的吸气剂,它应该返回什么? PositionPosition*Position&? 我应该将Position positionPosition* position作为实例变量吗?还是Position& position? 感谢。

1 个答案:

答案 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;
}

我认为就是这样。我花了很长时间写这个(可能是不必要的详细)答案比我想要的。希望它有所帮助。