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

时间:2013-05-21 19:02:41

标签: c++

我正在设计一个包装类来提供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
}

3 个答案:

答案 0 :(得分:5)

两者都以不好的方式打破。

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;所以你的结果可能会有所不同。

答案 1 :(得分:2)

你为什么要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,它带有自定义删除器,实际上实现了相同的功能,并且还适当地实现了三个规则。

答案 2 :(得分:2)

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

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