28

我想将 a 传递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 & aPtrunique_ptr<AClass> const & aPtr,它会编译,但是我可以调用类似的函数aPtr->changeAClass(),这是我不想允许的。

4

2 回答 2

40

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

当您将 a 传递const unique_ptr<T>&给函数时(与是否T存在无关const),它的实际含义是该函数承诺永远不会修改自身(但如果is not unique_ptr,它仍然可以修改指向的对象),即。不会有任何可能的所有权转让。您只是将用作裸指针周围的无用包装器。Tconstunique_ptr

因此,正如@MarshallClow 在评论中建议的那样,您应该摆脱包装器并传递裸指针或直接引用。这样做的好处是您的代码现在在语义上是清晰的(您的函数的签名清楚地表明它不会与所有权混淆,这对于 a并不立即显而易见const unique_ptr<...>&)并且它同时解决了您的“约束”问题!

即:

void someFunction(const AClass* p) { ... }

std::unique_ptr<AClass> ptr(new AClass());
someFunction(ptr.get());

编辑:解决您的第二个问题“为什么编译器不让我...unique_ptr<A>转换为unique_ptr<A const> ”。

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

std::unique_ptr<A> p(new A());
std::unique_ptr<const A> q(std::move(p));

但正如您所看到的,这意味着所有权从 转移pq

您的代码的问题是您将 (reference to)unique_ptr<const A> 传递给 function。由于与 存在类型差异unique_ptr<A>,为了使其工作,编译器需要实例化一个临时的。但是除非您通过 using 手动转移所有权,否则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>)并将该临时副本绑定到一个const引用。

我想这或多或少涵盖了它,尽管我的解释缺乏标准术语,并且可能没有应有的清晰。:)

于 2013-05-07T18:06:51.620 回答
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>

如果您出于某种原因特别想避免上述 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

于 2016-02-15T22:30:05.280 回答