C ++智能指针可以完全封装其数据吗?

时间:2011-03-19 12:54:16

标签: c++ smart-pointers

是否可以将原始C ++指针包装在类似智能指针的类中,这将允许用户使用熟悉的运算符(如数组和间接)进行更新:

int i;
my_ptr<int> ptr(i);
ptr[i] = 42;

但是,绝对会拒绝用户访问底层的原始地址。所以,这不应该成功:

int *p;
p = &ptr[i];
唉,我担心我可能会问不可能的事。我可以使用getter和setter方法,但我很好奇我是否可以不用。

8 个答案:

答案 0 :(得分:5)

通过代理类过滤访问权限,例如此(不完整示例):

template<typename T>
class proxy
{
    proxy(T & v) :value_(v) {}
    proxy & operator=(const T & v) { value_ = v; return *this; }
private:
    T & value_;
};

该课程需要更多工作,但一旦完成,如果您的operator[]返回其中一个,则可以从T分配,但您无法获取T的地址{1}}。

修改

感谢投票人。但这个答案并不像你想象的那么好。它不允许用户执行除代理中定义的操作之外的任何其他操作。因此,例如,这不起作用:

my_ptr<int> ptr(x);
...
ptr[i]++;

除非代理类专门针对每种类型,并且谁想要这样做?

答案 1 :(得分:1)

即使你成功地压制了get()方法,你也无法阻止人们这样做:

smart_ptr<C> ptr(new C);
C* raw = ptr.operator->();

ptr.operator->().operator->()

等,根据需要。两个代理之间的关系并不重要:为了使语法ptr->; f()起作用,这个序列最终必须以原始指针为底。

答案 2 :(得分:0)

我相信STL迭代器是如何尽可能透明地完成它,同时保留附加功能的很好的例子。过载最奇怪的是->运算符。

答案 3 :(得分:0)

不,它无法完成。您不能同时拥有启用ptr[i] = 42和禁止int* p = &ptr[i]的功能。你怎么能?第一个需要直接访问数据,这也必然会提供引用数据的可能性。

答案 4 :(得分:0)

没有。我认为最好的选择是在文档中编写它,并希望使用指针类的人坚持你的规则。

答案 5 :(得分:0)

你不能这样做。使用代码ptr[i],您可以获得类型为T的值的引用。您可以随意获取此地址。

答案 6 :(得分:0)

我会厌恶这样做的人!

正确的封装要求只使用必要的最少信息定义方法,以便它可以完成任务,而不再需要。

让我们采用假设类型struct Foo { unsigned id; std::string name; };

现在,让我们定义一个简单(直接)的方法对来返回id的{​​{1}}。

  • Foo
  • unsigned getId(Foo const& foo) { return foo.id; }

这些方法中是否需要知道是否在堆栈上分配了unsigned getId(Foo const* foo) { return foo ? foo->id : 0; }?在Foo内还是在智能指针内?

不,它没有。我当然只需要指针或引用来完成它的堆栈,并且该引用在方法的持续时间内是有效的。它不需要关心对象的生命周期。

还存在......界面爆炸的问题。您是否想为世界上任何类型的智能指针编写此方法的重载?我没有。

答案 7 :(得分:0)

我觉得给我一个答案很爽,但是在提出问题后我才想到了,这给了我一个获得反馈的新机会。我包含了代码和一些微基准测试结果。

所以,我的诀窍是[]运算符及其类似返回智能指针的类型,而不是元素类型和值。我推断按值返回4字节的my_ptr并不比4字节的元素引用更差。

inline my_ptr operator[](int i) const { return my_ptr(m_p+i); }

我只添加了一些基本操作符,但我认为可以根据需要继续使用该模式。也许Boost已经有类似的东西?

template <typename T>
class my_ptr {
  T *m_p;
public:
  inline my_ptr(T *p) : m_p(p) {}
  inline my_ptr operator[](int i) const { return my_ptr(m_p+i); } // By value
  inline my_ptr & operator= (T x) { *m_p = x; }
  inline my_ptr & operator= (my_ptr &x) { *m_p = *x.m_p; }
  inline my_ptr & operator+=(const my_ptr &x) { *m_p = *m_p + *x.m_p; }
  inline friend ostream &operator<<(ostream &o, const my_ptr &x) {
    o << *x.m_p;
    return o;
  }
};

我通过使用gcc 4.4.5和-O3开关将SIZE元素阵列NRUNS时间相加来进行基准测试,并且SIZE和NRUNS等于1 <&lt;&lt; 15.在我糟糕的机器上两个需要3.45秒。内核看起来像这样:

s += data[i];

大部分代码都在这里:

#define NRUNS (1<<15)
#define SIZE  (1<<15)
double data[SIZE];

int main(int argc, char *argv[])
{
  double s, t1, t2;
  my_ptr<double> s2(&s);
  my_ptr<double> data2(data);

  s = 0;
  t1 = omp_get_wtime();
  for (int n = 0; n < NRUNS; n++)
  for (int i = 0; i < SIZE; ++i)
    s += data[i];
  t2 = omp_get_wtime();
  cout << "sum=" << s << " " << "t2-t1=" << t2-t1 << "secs." << endl;

  s2 = 0;
  t1 = omp_get_wtime();
  for (int n = 0; n < NRUNS; n++)
  for (int i = 0; i < SIZE; ++i)
    s2 += data2[i];
  t2 = omp_get_wtime();
  cout << "sum=" << s2 << " " << "t2-t1=" << t2-t1 << "secs." << endl;
  return 0;
}

我还添加了一个受Benjamin Lindley的“代理”解决方案启发的版本。 添加到main的声明是:

proxy<double> s3(s);  
my_ptr2<double> data3(data);

代理类和智能指针(my_ptr2)声明为:

template<typename T>
struct proxy
{
  proxy(T & v) :value_(v) {}
  proxy & operator =(const T & v) { value_  = v; return *this; }
  proxy & operator+=(const T & v) { value_ += v; return *this; }
  proxy & operator+=(const proxy & x) { value_ += x.value_; return *this; }
private:
  T & value_;
};

template <typename T>
class my_ptr2 {
  T *m_p;
public:
  inline my_ptr2(T *p) : m_p(p) {}
  inline proxy<T> operator[](int i) const {
    return proxy<T>(m_p[i]);
  }
  inline friend ostream &operator<<(ostream &o, const my_ptr2 &x) {
    o << *x.m_p;
    return o;
  }
};

内核的核心现在是:

s = 0;
t1 = omp_get_wtime();
for (int n = 0; n < NRUNS; n++)
for (int i = 0; i < SIZE; ++i)
  s3 += data3[i];
t2 = omp_get_wtime();
cout << "sum=" << s << " " << "t2-t1=" << t2-t1 << "secs." << endl;

并且使用-O3,它的运行速度与其他两个版本一样快。我喜欢它,因为一旦开发,它会明确地将智能指针的更新与其目标数据分开。 另一方面,对用户来说可能感觉有点麻烦。这可能仍然可以接受:假设我的初始条件是绝对不能直接访问指针。