为什么std::unique_ptr没有const get方法?

12

我知道std::unique_ptr是现在这个样子的,也不会因为向后兼容性而改变,但我想知道规范的编写者为什么没有重载get方法,使其具有类似以下的const变体:

const T* get() const;

为了遵循unique_ptrconst意图,最好的猜测是它试图模仿指针并像T* const一样运作,而不是像一个典型的类。作为后续问题,如果我想以类似于常量的方式在我的类的常量实例中持有指针,我应该使用除std::unique_ptr之外的其他东西来持有数据吗?

更新

在我的情况下,我希望保护自己免受在类本身中误用指针的影响。我正在编写一个常量移动构造函数MyClass(const MyClass&& other),并通过std::copy将新实例中的数据复制到其他实例中。由于我假定复制一定是正确的,因为有了常量保护,所以追踪错误花费了很长时间。我正在努力找出除了提供一个常量getter并在进行复制时在类内部使用它之外,我还能做什么来保护自己。


关于后面的问题,std::unique_ptr<YourClass const> 不是你所寻求的吗?还是我误解了问题的那一部分。 - WhozCraig
在我的情况下,假设我只是包装一些数据,比如结构体,所以它只是一个 char[] 和一个 int 来表示长度。我想传递它并让消费者操作字节,除非他们有一个 const 实例。即使我为数据提供了 const 和 non-const 的 getter,我也希望确保我的内部方法不会干扰传递给我的类方法的 const 实例上的数据。 - quittle
1
我不理解你的更新。这似乎是一个XY问题,或者完全不同的问题。你到底为什么想要一个const移动构造函数?而“const保护”又是什么意思? - Oktalist
@Oktalist 如果我让你感到困惑,我很抱歉。我的更新是为了回到我要解决的问题,即如何防止自己再次出现相同的错误。const移动构造函数可能是我设计上的一个糟糕选择,但请考虑任何通过引用接受const实例的成员函数,在其中我会从中提取数据。所谓的“const保护”,是指我安全地避免修改由const引用控制的任何内容,以便在调试时,我可以跳过将const方法视为罪魁祸首的步骤,因为编译器会阻止我进行任何修改。 - quittle
1
你的const移动构造函数没有任何意义,如果我理解正确的话,你只是实现了一个复制构造函数。如果在你的类中移动构造函数没有意义,那没关系,不要创建一个,没有必要去hack什么... - tr3w
显示剩余3条评论
5个回答

6
智能指针假装是一个原始指针。 如果您有一个类成员是原始指针并在const方法中使用它,则无法更新指针,但可以修改所指向的对象。 智能指针希望具有相同的行为。因此,std :: unique_ptr :: get 是一个const方法,但不强制返回指向const对象的指针。
还要注意,您可以拥有指向const对象的指针。
MyClass *pointerToObject
std::unique_ptr<MyClass> smartPointerToObject;

// but you can have also a case
const MyClass *pointerToConstObject
std::unique_ptr<const MyClass> smartPointerToConstObject;

在最后一种情况下,std::unique_ptr::get将返回您期望的内容。
根据下面的评论:
只需提供私有方法:
InnerClass& GetField() { return *uniquePtrToInnerClass; }
const InnerClass& GetField() const { return *uniquePtrToInnerClass; }

在您的代码中使用它,您将拥有内部类的const对象,在const方法中。


我希望即使实例是可变的,其内容也可以是可变的,而在实例不可变时则为不可变的,这样我就无法知道在实例化类或存储数据时是否将其存储为const或非const版本。 - quittle
std::unique_ptr<const MyClass> 不能满足你的需求吗?另外,你在注释中写了如此复杂的句子,以至于我无法理解它。 - Marek R
它不能满足我的需求。考虑到有 OuterInner,其中 Outer 包含一个 unique_ptr<Inner>。有时 Outer 需要操作 Inner 实例,但在 const 方法中操作 Inner 将违反 Outer 的功能。 - quittle

5
没有必要通过其unique_ptr提供只读访问对象。当您传递unique_ptr并且需要传输所有权时才会使用它,对于不需要传输所有权的对象访问,请调用up.get()并将const T*传递给应仅读取的函数(如果指针从未为nullptr,那么评估*(up.get())并将const T&传递也是合理的)。
作为福利,这允许您在堆栈上存储的对象、嵌入在另一个对象中或使用除unique_ptr之外的智能指针管理的对象中使用该函数。
此处有一个关于所有unique_ptr参数传递情况(in/out,const/non-const等)的良好讨论:

1
我试图解决的问题不是传递unique_ptr,而是在访问const实例时在类内部保护自己。我可以为每个类中的每个智能指针添加一个const get来始终保护自己,但那会增加很多额外的代码。 - quittle
我猜他的意思是,即使将该成员函数标记为“const”,您仍然可以从“const”成员函数突变unique_ptr成员的内部。如果没有unique_ptr(例如,如果存储原始指针而不是unique_ptr),则无法这样做。 - bobobobo
@bobobobo:如果你有一个原始指针,你仍然可以改变所指向的对象。指针类型应该是T* const而不是const T*。你不能替换指针(但get()并没有提出要替换指针)。 - Ben Voigt

5
由于同样的原因,解引用 T*const 得到的是 T&,而不是 T const&
指针的 const 属性不同于所指对象的 const 属性。 get 是 const 的,它不修改 unique_ptr 的状态。
它的 const 属性不影响其内容的 const 属性。
有一种智能指针的概念可以传播 const 属性,但 unique_ptr 不是那个东西。 std::experimental::propogate_const 包装了一个类似指针的对象,并使 const 通过它传递。
它或类似物可能会解决您的问题。
请注意,当我尝试像这样传播 const 时,我发现有一半的时间我是错误的。但这在这里可能不是这种情况。
通常,以 const 方式处理 T * const 内容的正确方法是传递 T const&(或可为空的变量 T const*)。

2
std::experimental::propagate_const<std::unique_ptr<T>> 的翻译是什么? - Daniel Schepler
1
你只是在重复问题,它说:“我最好的猜测是它试图镜像指针并像T* const一样行事,而不是像典型的类一样。” - Ben Voigt
propogate_const 看起来有些有前途,但在我的情况下使用它会导致一种奇怪的模式,即在访问 const 方法中的 unique_ptrs 时始终将调用包装在此调用中。这更接近我想要的,但在每个使用 unique_ptr 的类中使用仍然相当冗长。 - quittle
2
@quittle 这个“深度const”不应该是一个“无处不在的东西”吧?你可以在任何情况下编写别名——template<class T> using flat_ptr = std::experimental::propotate_const<std::unique_ptr<T>>。这个类只有在包含逻辑const指针的类中使用才有意义;其他使用unique_ptr的地方不应该使用它。 - Yakk - Adam Nevraumont

1
我认为这个担忧是合理的,每个取消引用函数应该有2个版本。
e.g.
    const T* get() const;
    T* get();
    enter code here

我知道提供"T* get() const"的目的是为了方便替换现有的原始指针用法。但由于uniq ptr表示所有权,通过不可变(const)引用修改对象所拥有的某些东西是不正确的[假设修改完全由对象拥有与修改对象本身相同-如果这是一个对象而不是指针,则为真]。也许最好的选择是std提供另一个版本的Uniq ptr,它遵循上述习语(唯一的其他选项可能是从uniq ptr派生一个新类并为解除引用提供2个版本)。

0

对于 unique_ptr获取内部原始指针的引用是一个const操作。调用 .get() 并检索 std::unique_ptr 的内部原始指针不会改变 std::unique_ptr 对象本身的内部状态。因此,似乎库设计人员选择将其标记为const,而没有关注如果他们返回一个直接的非const引用会发生什么。

事实上,如果你有一个 std::unique_ptr 在一个对象内部,并且您调用该对象的const成员函数,则你仍然可以在该对象内部的std::unique_ptr 上调用非const成员函数。例如:

struct A {
    void Const() const { }
    void nonConst() { }
};

struct B {
    std::unique_ptr<A> a;
    void go() const {
        a->nonConst(); // OK
    }
};

虽然您无法在对象的const成员函数中对其内部状态变量执行非const操作,但并没有规定您不能对其他对象执行非const操作。

您可能期望的是const承诺从unique_ptr延续到对其内部指向的访问也适用,因此您希望unique_ptr的编写方式类似于以下内容:

template <typename T>
class cunique_ptr {
    T* ptr;
public:
    cunique_ptr() {
        ptr = new T;
    }
    ~cunique_ptr() {
        delete ptr;
    }

    // You can only get a non-const pointer to the internals from a non-const object
    T* get() { return ptr; }

    // The const member function carries over the const promise to access to its internals
    const T* get() const { return ptr; }
};

void test() {
    cunique_ptr<A> a;
    a.get()->nonConst();

    const cunique_ptr<A> ca;
    //ca.get()->nonConst(); //X fails: cannot call non-const member functions from const object
    ca.get()->Const();
}

然而,似乎库设计者选择放弃了那种类型的保护,并让const承诺表面上看起来有些肤浅。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接