C++ 智能指针常量正确性

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2079750/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 22:02:40  来源:igfitidea点击:

C++ smart pointer const correctness

c++constshared-ptr

提问by user231536

I have a few containers in a class, for example, vector or map which contain shared_ptr's to objects living on the heap.

我在一个类中有几个容器,例如,vector 或 map,它们包含 shared_ptr 到位于堆上的对象。

For example

例如

template <typename T>
class MyExample
{
public:

private:
    vector<shared_ptr<T> > vec_;
    map<shared_ptr<T>, int> map_;
};

I want to have a public interface of this class that sometimes returns shared_ptrs to const objects (via shared_ptr<const T>) and sometimes shared_ptr<T>where I allow the caller to mutate the objects.

我想要这个类的公共接口,它有时将 shared_ptrs 返回给 const 对象(通过shared_ptr<const T>),有时shared_ptr<T>我允许调用者改变对象。

I want logical const correctness, so if I mark a method as const, it cannot change the objects on the heap.

我想要逻辑常量的正确性,所以如果我将一个方法标记为常量,它就不能更改堆上的对象。

Questions:

问题:

1) I am confused by the interchangeability of shared_ptr<const T>and shared_ptr<T>. When someone passes a shared_ptr<const T>into the class, do I:

1) 我对shared_ptr<const T>和的互换性感到困惑shared_ptr<T>。当有人通过 ashared_ptr<const T>进入课堂时,我是否:

  • Store it as a shared_ptr<T>or shared_ptr<const T>inside the container?
  • OR
  • Do I change the map, vector types (e.g. insert_element(shared_ptr<const T>obj)?
  • 将其存储在容器中shared_ptr<T>shared_ptr<const T>内部?
  • 或者
  • 我是否更改地图、向量类型(例如 insert_element( shared_ptr<const T>obj))?

2) Is it better to instantiate classes as follows: MyExample<const int>? That seems unduly restrictive, because I can never return a shared_ptr<int>?

2)是否有更好的实例化类如下:MyExample<const int>?这似乎过于严格,因为我永远无法返回shared_ptr<int>?

回答by Terry Mahaffey

shared_ptr<T>and shared_ptr<const T>are notinterchangable. It goes one way - shared_ptr<T>is convertable to shared_ptr<const T>but not the reverse.

shared_ptr<T>shared_ptr<const T>不是可以互换的。它有一种方式 -shared_ptr<T>可以转换为shared_ptr<const T>但不能相反。

Observe:

观察:

// f.cpp

#include <memory>

int main()
{
    using namespace std;

    shared_ptr<int> pint(new int(4)); // normal shared_ptr
    shared_ptr<const int> pcint = pint; // shared_ptr<const T> from shared_ptr<T>
    shared_ptr<int> pint2 = pcint; // error! comment out to compile
}

compile via

编译通过

cl /EHsc f.cpp

cl /EHsc f.cpp

You can also overload a function based on a constness. You can combine to do these two facts to do what you want.

您还可以基于常量重载函数。你可以结合做这两个事实来做你想做的。

As for your second question, MyExample<int>probably makes more sense than MyExample<const int>.

至于你的第二个问题,MyExample<int>可能比MyExample<const int>.

回答by mmmmmmmm

I would suggest the following methotology:

我建议以下方法论:

template <typename T>
class MyExample
{
  private:
    vector<shared_ptr<T> > data;

  public:
    shared_ptr<const T> get(int idx) const
    {
      return data[idx];
    }
    shared_ptr<T> get(int idx)
    {
      return data[idx];
    }
    void add(shared_ptr<T> value)
    {
      data.push_back(value);
    }
};

This ensures const-correctness. Like you see the add() method does not use <const T> but <T> because you intend the class to store Ts not const Ts. But when accessing it const, you return <const T> which is no problem since shared_ptr<T> can easily be converted to shared_ptr<const T>. And sice both get() methods return copies of the shared_ptr's in your internal storage the caller can not accidentally change the object your internal pointers point to. This is all comparable to the non-smart pointer variant:

这确保了常量正确性。就像您看到的 add() 方法不使用 <const T> 而是使用 <T> 因为您打算让类存储 Ts 而不是 const Ts。但是当访问它 const 时,你返回 <const T> 这没有问题,因为 shared_ptr<T> 可以很容易地转换为 shared_ptr<const T>。并且这两个 get() 方法都返回内部存储中 shared_ptr 的副本,调用者不会意外更改内部指针指向的对象。这一切都可以与非智能指针变体相媲美:

template <typename T>
class MyExamplePtr
{
  private:
    vector<T *> data;

  public:
    const T *get(int idx) const
    {
      return data[idx];
    }
    T *get(int idx)
    {
      return data[idx];
    }
    void add(T *value)
    {
      data.push_back(value);
    }
};

回答by SoapBox

If someone passes you a shared_ptr<const T>you should never be able to modify T. It is, of course, technically possible to cast the const Tto just a T, but that breaks the intent of making the Tconst. So if you want people to be able to add objects to your class, they should be giving you shared_ptr<T>and no shared_ptr<const T>. When you return things from your class you do not want modified, that is when you use shared_ptr<const T>.

如果有人通过了shared_ptr<const T>你,你应该永远无法修改T. 当然的,技术上可以投的const T只是一个T,但断裂的意图作出的Tconst。因此,如果您希望人们能够向您的类添加对象,他们应该给您shared_ptr<T>而不是shared_ptr<const T>. 当您从类中返回不想修改的内容时,即使用shared_ptr<const T>.

shared_ptr<T>can be automatically converted (without an explicit cast) to a shared_ptr<const T>but not the other way around. It may help you (and you should do it anyway) to make liberal use of constmethods. When you define a class method const, the compiler will not let you modify any of your data members or return anything except a const T. So using these methods will help you make sure you didn't forget something, and will help users of your class understand what the intent of the method is. (Example: virtual shared_ptr<const T> myGetSharedPtr(int index) const;)

shared_ptr<T>可以自动转换(没有显式转换)到 ashared_ptr<const T>但不能反过来。自由地使用const方法可能会帮助您(无论如何您都应该这样做)。当您定义类方法时const,编译器不会让您修改任何数据成员或返回除 a 之外的任何内容const T。因此,使用这些方法将帮助您确保您没有忘记某些东西,并帮助您的类的用户了解该方法的意图是什么。(实施例:virtual shared_ptr<const T> myGetSharedPtr(int index) const;

You are correct on your second statement, you probably do not want to instantiate your class as <const T>, since you will never be able to modify any of your Ts.

您的第二条语句是正确的,您可能不想将类实例化为<const T>,因为您将永远无法修改任何Ts。

回答by Evan Teran

one thing to realize is that:

需要意识到的一件事是:

tr1::shared_ptr<const T>is mimicking the functionality of T const *namely what it points to is const, but the pointer itself isn't.

tr1::shared_ptr<const T>正在模仿T const *它指向的功能是const,但指针本身不是。

So you can assign a new value to your shared pointer, but I would expect that you wouldn't be able to use the dereferenced shared_ptras an l-value.

因此,您可以为共享指针分配一个新值,但我希望您不能将取消引用shared_ptr用作左值。

回答by Daniel Trugman

Prologue

序幕

The constqualifier changes the behaviour of std::shared_ptr, just like it affects the legacy C pointers.

const预选赛改变的行为std::shared_ptr,就像它会影响传统的C指针。

Smart pointers should be managed and stored using the right qualifiers at all times to prevent, enforce and help programmers to treat them rightfully.

应该始终使用正确的限定符来管理和存储智能指针,以防止、强制执行和帮助程序员正确对待它们。

Answers

答案

  1. When someone passes a shared_ptr<const T>into the class, do I store it as a shared_ptr<T>or shared_ptr<const T>inside the vector and map or do I change the map, vector types?
  1. 当有人将 a 传递给shared_ptr<const T>类时,我是将它存储为 ashared_ptr<T>还是shared_ptr<const T>在矢量和地图中,还是更改地图、矢量类型?

If your API accepts a shared_ptr<const T>, the unspoken contract between the caller and yourself is that you are NOT allowed to change the Tobject pointed by the pointer, thus, you have to keep it as such in your internal containers, e.g. std::vector<std::shared_ptr<const T>>.

如果您的 API 接受shared_ptr<const T>,则调用者与您之间的不成文约定是不允许您更改T指针指向的对象,因此,您必须将其保持在内部容器中,例如std::vector<std::shared_ptr<const T>>.

Moreover, your module should NEVER be able/allowed to return std::shared_ptr<T>, even though one can programatically achieve this (See my answer to the second question to see how).

此外,您的模块永远不能/允许返回std::shared_ptr<T>,即使可以通过编程实现这一点(请参阅我对第二个问题的回答以了解如何)。

  1. Is it better to instantiate classes as follows: MyExample<const int>? That seems unduly restrictive, because I can never return a shared_ptr<int>?
  1. 它是更好地实例化类如下:MyExample<const int>?这似乎过于严格,因为我永远无法返回shared_ptr<int>?

It depends:

这取决于:

  • If you designed your module so that objects passed to it should not change again in the future, use const Tas the underlying type.

  • If you your module should be able to return non-const Tpointers, you should use Tas your underlying type and probably have two different getters, one that returns mutable objects (std::shared_ptr<T>) and another that returns non-mutable objects (std::shared_ptr<const T>).

  • 如果您将模块设计为传递给它的对象将来不应再次更改,请const T用作基础类型。

  • 如果您的模块应该能够返回非常量T指针,那么您应该将其T用作基础类型,并且可能有两个不同的 getter,一个返回可变对象 ( std::shared_ptr<T>),另一个返回非可变对象 ( std::shared_ptr<const T>)。

And, even though I hope we just agreed you should notreturn std::shared_ptr<T>if you have a const Tor std::shared_ptr<const T>, you can:

而且,即使我希望我们刚刚同意,如果您有或,则不应返回,但您可以std::shared_ptr<T>const Tstd::shared_ptr<const T>

const T a = 10;
auto a_ptr = std::make_shared<T>(const_cast<T>(a));

auto b_const_ptr = std::make_shared<const T>();
auto b_ptr = std::const_pointer_cast<T>(b_const_ptr);

Full blown example

完整的例子

Consider the following example that covers all the possible permutations of constwith std::shared_ptr:

考虑下面的例子,它涵盖了constwith 的所有可能的排列std::shared_ptr

struct Obj
{
  int val = 0;
};

int main()
{
    // Type #1:
    // ------------
    // Create non-const pointer to non-const object
    std::shared_ptr<Obj> ptr1 = std::make_shared<Obj>();
    // We can change the underlying object inside the pointer
    ptr1->val = 1;
    // We can change the pointer object
    ptr1 = nullptr;

    // Type #2:
    // ------------
    // Create non-const pointer to const object
    std::shared_ptr<const Obj> ptr2 = std::make_shared<const Obj>();
    // We cannot change the underlying object inside the pointer
    ptr2->val = 3; // <-- ERROR
    // We can change the pointer object
    ptr2 = nullptr;

    // Type #3:
    // ------------
    // Create const pointer to non-const object
    const std::shared_ptr<Obj> ptr3 = std::make_shared<Obj>();
    // We can change the underlying object inside the pointer
    ptr3->val = 3;
    // We can change the pointer object
    ptr3 = nullptr; // <-- ERROR

    // Type #4:
    // ------------
    // Create const pointer to non-const object
    const std::shared_ptr<const Obj> ptr4 = std::make_shared<const Obj>();
    // We can change the underlying object inside the pointer
    ptr4->val = 4; // <-- ERROR
    // We can change the pointer object
    ptr4 = nullptr; // <-- ERROR

    // Assignments:
    // ------------
    // Conversions between objects
    // We cannot assign to ptr3 and ptr4, because they are const
    ptr1 = ptr4 // <-- ERROR, cannot convert 'const Obj' to 'Obj'
    ptr1 = ptr3;
    ptr1 = ptr2 // <-- ERROR, cannot convert 'const Obj' to 'Obj'

    ptr2 = ptr4;
    ptr2 = ptr3;
    ptr2 = ptr1;
}

Note: The following is true when managing all types of smart pointers. The assignment of pointers might differ (e.g. when handling unique_ptr), but the concept it the same.

注意:以下适用于管理所有类型的智能指针。指针的分配可能不同(例如在处理时unique_ptr),但概念是相同的。