【问题标题】:Header-only library circular dependency仅头文件库循环依赖
【发布时间】:2013-01-08 14:23:38
【问题描述】:

我正在尝试围绕外部 C API 创建一个仅包含标头的 C++ 库。 C API 使用void * 指针作为句柄。 想法是这样的:

// resource.hpp
class Resource {
public:
    // RAII constructor, destructor, etc.
    // ...
    void do_something(const Handle & h) {
        do_something_impl( (void *) h);
    }
};

// handle.hpp
class Handle
{
public:
    Handle(size_t n, const Resource & res)
        : p_(res.allocate(n)), res_(res) {}

    // cast operation
    operator void *() const { return p_; }

private:
    void * p_;
    Resource & res_;
};

这里的问题是 (a) Handle 必须保持对 Resource 的引用,并且 (b) Resource 需要能够将 Handle 转换为 void *。不幸的是,这会导致循环依赖。

关于如何重组这个有什么想法吗?

注意:答案不是简单地“包含 xxx.hpp”或前向声明其中一个类。 这需要以某种方式重组,我只是不太明白如何。

class Handle 作为前向声明添加到资源文件的顶部不起作用,因为(void *) 转换是资源仍然看不到的句柄定义的一部分。同样,将强制转换更改为 void * ptr() 成员函数会导致同样的问题。

将函数定义移动到 .cpp 文件也不是一个答案——它必须是仅标题。

【问题讨论】:

标签: c++ circular-dependency raii


【解决方案1】:

嗯,这是救援的模板(再次!):

// resource.hpp
class Resource;
template<typename TResource> class Handle;

class Resource {
public:
    // RAII constructor, destructor, etc.
    // ...
    void do_something(const Handle<Resource> & h) {
        do_something_impl( (void *) h);
    }
};

// handle.hpp
template<class TResource>
class Handle {
public:
    Handle(size_t n, const TResource & res)
        : p_(res.allocate(n)), res_(res) {}

    // cast operation
    operator void *() const { return p_; }

private:
    void * p_;
    TResource & res_;
};

【讨论】:

    【解决方案2】:

    不要将头文件包含在彼此中,而是有一个 前向声明 类。这样它们就可以用作引用或指针。

    所以在resource.hpp中:

    class Handle;
    
    class Resource {
    public:
        // RAII constructor, destructor, etc.
        // ...
        void do_something(const Handle & h) {
            do_something_impl( (void *) h);
        }
    };
    

    在handle.hpp中:

    class Resource;
    
    class Handle
    {
    public:
        Handle(size_t n, const Resource & res)
            : p_(res.allocate(n)), res_(res) {}
    
        // cast operation
        operator void *() const { return p_; }
    
    private:
        void * p_;
        Resource & res_;
    };
    

    由于您在do_something 函数中使用了void* 类型转换运算符,因此您必须将该实现移动到单独的源文件中。在该源文件中,您可以包含两个头文件,因此可以访问所有函数。

    【讨论】:

    • @Joachim--在 vs2010 中,这给出了“无法从 'const Handle & h' 转换为 'void *'; 源或目标的类型不完整。换句话说,资源看不到operator void *().
    • 另外,在handle.hpp中添加class Resource是行不通的,因为Handle需要看到Resource.allocate()成员函数!
    • @DavidH 然后你必须在handle.hpp 中包含resource.hpp 文件。只要你不做相反的事情也没关系。至于报错,是因为你试图将非指针类型转换为指针类型。
    • @Joachim--转换运算符的重点是专门将Handle类型转换为指针。你能建议一种不同的方式吗? void * ptr() 成员函数会给出相同的错误并导致相同的问题。
    • @DavidH 难道你不能只使用地址操作符来实际获得一个真正的指针吗?
    猜你喜欢
    • 2011-07-11
    • 1970-01-01
    • 2011-01-06
    • 2011-06-16
    • 2015-07-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多