RAII - 存储"void*&"或"void**"

RAII - store `void*&` or `void**`

本文关键字:void 存储 RAII      更新时间:2023-10-16

我正在设计一个包装器类来提供RAII功能。原始用例如下:

void* tid(NULL);
OpenFunc(&tid);
CloseFunc(&tid);

在我引入一个新的包装器类之后,我希望未来的用法如下:

void* tid(NULL);
TTTA(tid);

TTTB(tid);

问题:

TTTATTTB哪个实现更好?或者他们都不好,请介绍一个更好的。

我担心的一件事是,在资源分配后,id将在TTTATTTB类之外访问,直到id被破坏。根据我的理解,我的设计应该不会有副作用。

谢谢

class TTTA : boost::noncopyable
{
public:
    explicit TTTA(void *id)
        : m_id(id)
    {
        OpenFunc(&m_id); // third-party allocate resource API
    }
    ~TTTA()
    {
        CloseFunc(&m_id); // third-party release resource API
    }   
private:
    void* &m_id; // have to store the value in order to release in destructor
}
class TTTB : boost::noncopyable
{
public:
    explicit TTTB(void *id)
        : m_id(&id)
    {
        OpenFunc(m_id); // third-party allocate resource API
    }
    ~TTTB()
    {
        CloseFunc(m_id); // third-party release resource API
    }   
private:
    void** m_id; // have to store the value in order to release in destructor
}

//传入指针比较

class TTTD
{
public:
    TTTD(int* id)    // Take as reference, do not copy to stack.
        : m_id(&id)
    {
        *m_id = new int(40);
    }
private:
    int** m_id; 
};

class TTTC
{
public:
    TTTC(int* &id)    
        : m_id(id)
    {
        m_id = new int(30);
    }
private:
    int* &m_id; 
};
class TTTB
{
public:
    TTTB(int* id)    
        : m_id(id)
    {
        m_id = new int(20);
    }
private:
    int* &m_id; 
};
class TTTA
{
public:
    TTTA(int** id)    
        : m_id(id)
    {
        *m_id = new int(10);
    }
private:
    int** m_id; 
};

int main()
{
    //////////////////////////////////////////////////////////////////////////
    int *pA(NULL);
    TTTA a(&pA);
    cout << *pA << endl; // 10
    //////////////////////////////////////////////////////////////////////////
    int *pB(NULL);
    TTTB b(pB);
    //cout << *pB << endl; // wrong
    //////////////////////////////////////////////////////////////////////////
    int *pC(NULL);
    TTTC c(pC);
    cout << *pC << endl; // 30
    //////////////////////////////////////////////////////////////////////////
    int *pD(NULL);
    TTTD d(pD);
    cout << *pD << endl; // wrong
}

两者都是坏的。

TTTA存储对存储在堆栈上的变量(参数id)的引用。
TTTB存储一个指向存储在堆栈上的变量的指针。

这两次,当构造函数返回时,变量都超出了作用域。

编辑:既然你想要修改的值,最简单的修复是把指针作为引用;这将使TTTC引用实际指针,而不是将指针作为非引用形参时所做的本地复制;

class TTTC : boost::noncopyable
{
public:
    explicit TTTA(void *&id)    // Take as reference, do not copy to stack.
        : m_id(id)
...
private:
    void* &m_id; // have to store the value in order to release in destructor
}

破坏你的版本的简单测试是在类中添加一个print方法来打印指针值并执行;

int main() {
  void* a = (void*)0x200;
  void* b = (void*)0x300;
  {
    TTTA ta(a);
    TTTA tb(b);
    ta.print();
    tb.print();
  }
}

TTTA和TTTB在我的机器上都将两个值打印为0x300。当然,结果真的是UB;

你为什么要做tid呢?它将信息泄露给客户端,并使使用时间延长两倍(两行而不是一行):

class tttc {
    void* id;
public:
    tttc() {
        OpenFunc(&id);
    }
    ~tttc() {
        CloseFunc(&id);
    }
    tttc(tttc const&) = delete;
    tttc& operator =(tttc const&) = delete;
};

注意这个类禁止复制——你的解决方案违反了三个规则。

如果您需要从外部访问id,则提供tttc内部的转换:

void* get() const { return id; }

或者,如果绝对必要,通过隐式转换:

operator void*() const { return id; }

(但要谨慎使用,因为隐式转换削弱了类型系统,可能导致难以诊断的错误。)

然后在标准库中有std::unique_ptr,它使用自定义删除器,实际上实现了相同的功能,并且正确地实现了三规则

完全包装呢?这样,您就不必担心管理两个变量的生命周期,而只需管理一个变量。

class TTTC
{
    void* m_id;
public:
    TTTC()
        : m_id(nullptr)
    {
        OpenFunc(&m_id); // third-party allocate resource API
    }
    TTTC(TTTC const&) = delete; // or ensure copying does what you expect
    void*const& tid() const { return m_id; }
    ~TTTC()
    {
        CloseFunc(&m_id); // third-party release resource API
    }
};

使用它本身就是简单的:

TTTC wrapped;
DoSomethingWithTid(wrapped.tid());