访问智能指针时的自定义操作

时间:2011-12-24 14:40:16

标签: c++

在我们的项目中,我们在某些类中使用一种特殊类型的指针,在访问指针对象时执行一些自定义代码(在我们的例子中,修改某些类的内存管理)。代码看起来像这样:

#include <iostream>
#include <memory>

void do_something(int i) {
    std::cout << "+++ function(" << i << ") +++" << std::endl;
}

class C {
public:
    int method() {
        std::cout << "+++ C::method() +++" << std::endl;
        return 42;
    }
};

template <class T>
class ptr : public std::unique_ptr<T> {
public:
    class proxy {
    public:
        proxy(T* t) : m_t(t) { std::cout << ">>> Begin access >>>" << std::endl; }
        ~proxy() { std::cout << "<<< End access <<<" << std::endl; }

        T* operator->() { return m_t; }

    private:
        T* m_t;
    };

public:
    ptr(T* p) : std::unique_ptr<T>(p) {}

    proxy operator->() { return proxy(std::unique_ptr<T>::get()); }
};

int main() {
    ptr<C> pc(new C);
    pc->method();
    do_something(pc->method());     // <-- problem!
    return 0;
}

因此,当使用指针访问底层对象时,会返回一个临时代理对象,它通过在构造函数和析构函数中执行代码来修改指针访问行为(我想在行的末尾)。出现问题,例如当使用函数调用组合对指针的访问时,如上例中的代码。该程序产生以下输出:

>>> Begin access >>>
+++ C::method() +++
<<< End access <<<
>>> Begin access >>>
+++ C::method() +++
+++ function(42) +++
<<< End access <<<

如您所见,临时代理对象仅在调用function()后销毁。然而,这不是期望的行为(在我们的案例中,与内存管理混淆)。在这种情况下的解决方法是将访问的方法的结果保存在临时值中并将两个调用分开:

int result = pc->method();
do_something(result);

这会将代理对象的生命周期限制为实际的访问权限。但当然这有点容易出错,因为编译器允许组合两个函数调用,所以你很容易忘记它。

问题:您能想到一种方法来将代理对象的生命周期限制为实际访问(我认为这可能是不可能的),或者让编译器通知您错误地使用构造错误或警告?

1 个答案:

答案 0 :(得分:3)

首先,我真的不会unique_ptr继承,而是撰写。

接下来,您必须对客户端代码进行一些更改:更改指针的类型(并用包装器替换指针);或者将裸指针的参数类型更改为工作添加指针。

如果你坚持你的客户端函数接受裸T *,那么任何合适的转换都必须在调用者的范围内进行,所以你永远不会产生副作用“就像指针是解除引用的”。

有了这个,我个人会尝试采用一种方法,客户接受你的智能指针:

void do_something(WorkPointer<Foo> & p);

要实现WorkPointer,请替换取消引用运算符:

template <typename T> struct WorkPointer
{
    proxy operator->() { return proxy(m_p.get()); }
private:
    std::unique_ptr<T> m_p;
    // ...
};

如果你想在取消引用后工作,你仍然需要代理。如果不需要,您可以直接将额外的工作放入解除引用操作符。

我认为这个问题并没有真正明确定义,并且您可能永远不会依赖于“访问后”代码调用的确定时间。想象一下以下用途:

void some_function(WorkPointer<Foo> & p)
{
     Foo & f = *p;      // #1
     something_else(f);
}

现在,“之前”“之后”代码将在时间线#1运行时结束。由于这通常是指针的典型使用,因此您必须预见到这一点。但是,something_else(*p)是一个替代调用,它会在函数调用something_else结束后调用“after”代码。

我认为你不能以任何简单的“直接替换”的方式解决这个问题,所以你应该重新设计你的代码,这样就不需要这种双管齐下的控制。