通过减少填充标头的数量隐藏实施细节

时间:2016-08-18 08:48:24

标签: c++ c++11

我正在开发一个库。我有从外面调用的接口类。

我还有一个不应该从外面调用的内部引擎。

当我在这里和那里阅读时,我应该隐藏内部引擎类,甚至不填充它的标题。由于我有以下结构:

interface.hpp:

#include "engine.hpp" 
class interface{
private:
    enigne eng;
};

interface.cpp:

#include "engine.hpp"
//code that uses member variables and functions from eninge 

engine.hpp:

class engine{};

要解决填充“engine.hpp”的问题,我应该将代码更改为:

interface.hpp:

class engine;
class interface{
private:
    some_smart_pointer<enigne> eng_ptr;
};

interface.cpp:

#include "engine.hpp"
//code that uses member variables and functions from eninge 

enigne.hpp:

class engine{};

这解决了这个问题。但是,从现在起engine动态分配。它的所有成员变量都在免费商店中。

我无法理解我必须改变我的设计并在免费商店上分配引擎来解决隐藏实现细节的问题。有更好的解决方案吗?

P.S。我不是在问这个解决方案为何有效。我知道如果我把它留在堆栈上,知道引擎类的大小是强制性的。我的问题是要求一个可以解决问题的不同设计。

修改

interfaceengine都有成员变量。

4 个答案:

答案 0 :(得分:4)

您正在使用PIMPL惯用法。隐藏实现的另一种方法是使用接口,即抽象基类和工厂函数:

<强> interface.hpp:

class interface{
public:
    virtual ~interface(){}
    virtual void some_method() = 0;
};

// the factory function
static some_smart_pointer<interface> create();

<强> interface.cpp:

#include "interface.hpp"
#include "engine.hpp"

class concrete : public interface{
public:
    virtual void some_method() override { /* do something with engine */ }
private:
    engine eng;
};

some_smart_pointer<interface> create(){ return new concrete; }

<强>的main.cpp

#include "interface.hpp"

int main()
{
    auto interface = create();
    interface->some_method();

    return 0;
}

此处的缺点是您必须动态分配interface而不是engine

有关PIMPL和接口herehere

的更多讨论

修改

基于STL容器和Howard Hinnant的stack allocator,在免费商店中避免变量的第三种方法可以是:

<强> interface.hpp:

class interface{
public:
    interface();
    ~interface();

    // I disable copy here, but you can implement them
    interface(const interface&) = delete;
    interface& operator=(interface&) = delete;

private:
    engine* eng;
};

<强> interface.cpp:

#include "interface.hpp"
#include "engine.hpp"
#include "short_alloc.h"

#include <map>

namespace
{
    std::map<
        interface*,
        engine,
        std::default_order<interface*>,
        // use a stack allocator of 200 bytes
        short_alloc<std::pair<interface*, engine>, 200>
    > engines;
}

interface::interface():
    eng(&engines[this])
        // operator[] implicitly creates an instance and returns a reference to it
        // the pointer gets the address
{
}

interface::~interface()
{
    // destroy the instance
    engines.erase(this);
}

答案 1 :(得分:3)

  

我无法理解我必须改变我的设计并在免费商店上分配引擎来解决隐藏实现细节的问题。

您似乎已经自己解释过了:

  

我知道了解引擎类的大小

事实上。如果interfaceengine类型的成员,则必然需要知道该成员的大小 - 否则interface本身的大小无法知晓。不完整类型的大小无法知道。定义成员类型可以解决这个问题,但与您隐藏实现的愿望相冲突。

  

有更好的解决方案吗?

没有更好的解决方案。带有免费存储的PIMPL - 这是您目前使用的模式 - 尽可能好。

从技术上讲,PIMPL并不要求您使用免费商店。您可以将对象存储在任何您喜欢的位置。您可以使用静态存储。但这会限制您可以拥有的实例数量。您甚至可以将内存缓冲区分配为interface的成员,但是您需要对此类缓冲区的大小进行硬编码,并且它必须与engine的大小(和对齐方式)相匹配。

我将这两种理论建议归类为kludges - 特别是后者。如果您的分析表明特定额外分配的开销很大并且放弃PIMPL不是一种选择,那么静态存储可能是值得的。

答案 2 :(得分:2)

这是此问题的标准解决方案:它被命名为PIMPL(指向实现的指针)习惯用法。正如名称所说,它总是涉及到实现类的指针(或智能指针),因为 C ++根本不允许这样做:

Chrome 52.0.2743 (Windows 10 0.0.0) getUserDetails Should return user details of curently logged in user FAILED
        TypeError: Cannot read property 'access_token' of null
            at getUserDetails (tests.webpack.js:7589:48)
            at Context.<anonymous> (tests.webpack.js:14005:31)
Chrome 52.0.2743 (Windows 10 0.0.0): Executed 6 of 6 (1 FAILED) (0.115 secs / 0.056 secs)
npm ERR! Test failed.  See above for more details.

答案 3 :(得分:1)

隐藏实现而不强制在堆上分配对象的典型解决方案是将它们放在detail命名空间中,并将这些类型的标头放在详细子目录中

请注意,您的实现的某些方面将会公开,因为它会影响您的库的ABI。没有任何解决方案可以为您提供 ABI绝缘并避免在堆上分配对象。

正如你所描述的那样,你最好使用pimpl。