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

Custom operation when accessing a smart pointer

本文关键字:自定义 操作 智能 指针 访问      更新时间:2023-10-16

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

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

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

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

首先,我真的不会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);
}

现在"before""after"代码将在时间线#1结束时运行。由于这完全是指针的典型用法,因此您必须预料到这一点。然而,something_else(*p)是一种可选调用,它将导致在something_else函数调用结束后调用"after"代码。

我不认为你可以通过任何简单的"插入式替换"的方式来解决这个问题,所以你可能应该重新设计你的代码,这样它就不需要这种双管齐下的控制了。