是否可以将原始C ++指针包装在类似智能指针的类中,这将允许用户使用熟悉的运算符(如数组和间接)进行更新:
int i;
my_ptr<int> ptr(i);
ptr[i] = 42;
但是,绝对会拒绝用户访问底层的原始地址。所以,这不应该成功:
int *p;
p = &ptr[i];
唉,我担心我可能会问不可能的事。我可以使用getter和setter方法,但我很好奇我是否可以不用。
答案 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,它的运行速度与其他两个版本一样快。我喜欢它,因为一旦开发,它会明确地将智能指针的更新与其目标数据分开。 另一方面,对用户来说可能感觉有点麻烦。这可能仍然可以接受:假设我的初始条件是绝对不能直接访问指针。