如何使用unique_ptr将const指针传递给const对象

时间:2013-05-07 17:45:08

标签: c++ c++11 unique-ptr

我想将unique_ptr传递给辅助函数,我想确保辅助函数既不修改指针也不修改指针对象。没有unique_ptr,解决方案是

void takePtr(AClass const * const aPtr) {
  // Do something with *aPtr. 
  // We cannot change aPtr, not *aPtr. 
}

(嗯,从技术上讲,AClass const * aPtr就够了。)我可以用

来调用它
AClass * aPtr2 = new AClass(3);
takePtr(aPtr2);

我想改用unique_ptr,但无法弄清楚如何写这个。我试过了

void takeUniquePtr(unique_ptr<AClass const> const & aPtr) {
  // Access *aPtr, but should not be able to change aPtr, or *aPtr. 
}

当我用

打电话时
unique_ptr<AClass> aPtr(new AClass(3));
takeUniquePtr(aPtr);

它无法编译。我看到的错误是

testcpp/hello_world.cpp:141:21: error: invalid user-defined conversion from ‘std::unique_ptr<AClass>’ to ‘const std::unique_ptr<const AClass>&’ [-fpermissive]

unique_ptr<AClass>unique_ptr<AClass const>的转换不应该是自动的吗?我在这里错过了什么?

顺便说一下,如果我在函数定义中将unique_ptr<AClass const> const & aPtr更改为unique_ptr<AClass> const & aPtr,它会编译,但是我可以调用像aPtr->changeAClass()这样的函数,我不想让它。

2 个答案:

答案 0 :(得分:25)

智能指针用于管理所有权和生命周期,它们允许我们(除其他事项外)安全地转移代码各部分的所有权。

当你将const unique_ptr<T>&传递给一个函数时(与T是否const无关),它实际意味着该函数承诺永远不会修改{{1}本身(但如果unique_ptr不是T),它仍然可以修改指向对象,即。没有任何可能的所有权转让。您只是使用const作为裸指针周围的无用包装。

所以,正如@MarshallClow在评论中建议的那样,你应该摆脱包装并传递裸指针或直接引用。这很酷的是你的代码现在语义清晰(你的功能的签名清楚地表明它并没有弄乱所有权,这是立即显而易见unique_ptr),它解决了你的问题&#34;问题在同一时间!

即:

const unique_ptr<...>&

修改以解决您的第二个问题&#34; 为什么编译器不允许我......将void someFunction(const AClass* p) { ... } std::unique_ptr<AClass> ptr(new AClass()); someFunction(ptr.get()); 强制转换为unique_ptr<A>&#34;

实际上,您可以移动 unique_ptr<A const>unique_ptr<A>

unique_ptr<A const>

但正如您所知,这意味着将所有权从std::unique_ptr<A> p(new A()); std::unique_ptr<const A> q(std::move(p)); 转移到p

您的代码存在的问题是您将(引用)q 传递给函数。由于与unique_ptr<const A>存在类型差异,为了使其工作,编译器需要实例化临时。但除非您使用unique_ptr<A>手动转移所有权,否则编译器会尝试复制您的std::move,但由于unique_ptr明确禁止,因此无法执行此操作它

请注意如果移动unique_ptr

,问题将如何消失
unique_ptr

编译器现在能够构建一个临时void test(const std::unique_ptr<const int>& p) { ... } std::unique_ptr<int> p(new int(3)); test(std::move(p)); 并移动原始unique_ptr<const A>而不会打破您的期望(因为现在很清楚您想移动,而不是复制)。

因此,问题的根源是unique_ptr<A>只有移动语义而不是复制语义,但是您需要复制语义来创建临时然后保持所有权。鸡蛋和鸡肉,unique_ptr就是这样设计的。

如果您现在考虑unique_ptr 具有复制语义,问题也会消失。

shared_ptr

原因是编译器现在能够创建一个临时的void test(const std::shared_ptr<const int>& p) { ... } std::shared_ptr<int> p(new int(3)); test(p); //^^^^^ Works! 副本(从std::shared_ptr<const int>自动转换)并将该临时绑定到std::shared_ptr<int>参考

我想这或多或少涵盖了它,即使我的解释缺乏标准术语,也许不是应该如此清晰。 :)

答案 1 :(得分:2)

关于const智能指针的这个老问题。 以上答案忽略了简单的模板解决方案。

非常简单的模板选项(选项a)

template<class T>
void foo(const unique_ptr<T>& ptr) {
    // do something with ptr
}

此解决方案允许将unique_ptr的所有可能选项发送到foo:

  1. const unique_ptr<const int>
  2. unique_ptr<const int>
  3. const unique_ptr<int>
  4. unique_ptr<int>
  5. 如果您特别想因上述某些原因而避免使用上述3和4,请将const添加到T:

    只接受const / non-const unique_ptr到const! (选项b)

    template<class T>
    void foo(const unique_ptr<const T>& ptr) {
        // do something with ptr
    }
    

    旁注1

    如果您可以或不可以改变指向值的情况获得不同的行为,您可以重载“选项a”和“选项b”。

    旁注2

    如果您不想对此功能中的指向值进行任何更改(从不!我们获得的参数类型!) - 不要超载
    使用“选项b”,编译器将不允许更改我们指向的值。完成工作!
    如果你想支持所有4种情况,即“选项a”,该功能仍然可能“意外地”改变我们指向的值,例如

    template<class T>
    void foo(const unique_ptr<T>& ptr) {
        *ptr = 3;
    }
    

    然而,如果至少有一个调用者的T实际上是const,那么这应该不是问题,在这种情况下编译器不会喜欢它并帮助你解决问题。
    您可以在单元测试中添加这样的调用者,例如:

        foo(make_unique<const int>(7)); // if this line doesn't compile someone
                                        // is changing value inside foo which is
                                        // not allowed!
                                        // do not delete the test, fix foo!
    

    代码段:http://coliru.stacked-crooked.com/a/a36795cdf305d4c7