C ++智能指针const正确性

时间:2010-01-17 02:21:42

标签: c++ const shared-ptr

我在类中有一些容器,例如包含的vector或map shared_ptr是生活在堆上的对象。

例如

template <typename T>
class MyExample
{
public:

private:
 vector<tr1::shared_ptr<T> > vec;
 map<tr1::shared_ptr<T> , int> h;
};

我希望这个类的公共接口有时会返回shared_ptrs 到const对象(通过shared_ptr<const T>),有时shared_ptr<T>在哪里 我允许调用者改变对象。我想要逻辑const正确性,所以如果我标记 作为const的方法,它不能改变堆上的对象。

问题:

1)我对tr1::shared_ptr<const T>tr1::shared_ptr<T>的可互换性感到困惑。 当有人将shared_ptr<const T> shared_ptr传递到班级时,我是将其作为shared_ptr<T>shared_ptr<const T>存储在矢量和地图中,还是更改地图,矢量类型(例如insert_elemeent({ {1}} obj)?

2)如下实例化类更好:shared_ptr<const T>?那看起来 过度限制,因为我永远不会返回MyExample<const int>

5 个答案:

答案 0 :(得分:36)

shared_ptr<T>shared_ptr<const T> 可互换。这是一种方式 - shared_ptr<T>可转换为shared_ptr<const T>,但不是相反。

观察:

// 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
}

通过

编译
  

cl / EHsc f.cpp

您还可以根据常量重载函数。你可以结合起来做这两件事来做你想做的事。

至于你的第二个问题,MyExample<int>可能比MyExample<const int>更有意义。

答案 1 :(得分:11)

我会建议以下的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);
    }
};

这确保了正确性。就像你看到add()方法不使用&lt; const T&gt;但是&lt; T&gt;因为你打算让班级存储Ts而不是const Ts。但是当访问它时,你返回&lt; const T&gt;这是没有问题的,因为shared_ptr&lt; T&gt;可以很容易地转换为shared_ptr&lt; const T&gt;。并且两个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);
    }
};

答案 2 :(得分:5)

如果有人通过shared_ptr<const T>,您永远无法修改T。当然,技术上可以将const T强制转换为T,但这会打破制作T const的意图。因此,如果您希望人们能够向您的班级添加对象,那么他们应该为您提供shared_ptr<T>而不是shared_ptr<const T>。当您从班级返回内容时,您不需要修改,即使用shared_ptr<const T>时。

shared_ptr<T>可以自动转换(没有显式转换)到shared_ptr<const T>,但不是相反。它可以帮助你(并且你应该这样做)来自由地使用const方法。定义类方法const时,编译器不允许您修改任何数据成员或返回除const T之外的任何内容。因此,使用这些方法可以帮助您确保不会忘记某些内容,并且可以帮助您的班级用户了解该方法的用途。 (例如:virtual shared_ptr<const T> myGetSharedPtr(int index) const;

您的第二个陈述是正确的,您可能不希望将您的班级实例化为<const T>,因为您永远无法修改任何T

答案 3 :(得分:3)

要认识到的一点是:

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

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

答案 4 :(得分:0)

序言

const限定符会更改std::shared_ptr的行为,就像它会影响旧版C指针一样。

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

答案

  
      
  1. 当有人将shared_ptr<const T>传递给班级时,我是否将其存储为shared_ptr<T>shared_ptr<const T>在矢量和地图中,还是要更改地图,矢量类型? li>   

如果您的API接受shared_ptr<const T>,则调用者和您自己之间的不言而喻的约定是不允许您更改指针指向的T对象,因此,必须将其保留为例如在您的内部容器中std::vector<std::shared_ptr<const T>>

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

  
      
  1. 实例化类是否更好:MyExample<const int>?这似乎限制不严,因为我永远无法返回shared_ptr<int>
  2.   

这取决于:

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

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

而且,即使我希望我们只是同意您不应如果您有std::shared_ptr<T>const T,也应该返回std::shared_ptr<const T>,但您可以< / strong>:

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);

完整例子

请考虑以下示例,该示例涵盖conststd::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;
}

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