pimpl是否与匿名命名空间兼容?

时间:2011-04-21 14:38:50

标签: c++ namespaces pimpl-idiom

我正在尝试使用 pimpl 模式并在匿名命名空间中定义实现类。这在C ++中是否可行?我失败的尝试描述如下。

是否可以在不将实现移动到具有名称(或全局名称)的命名空间的情况下解决此问题?

class MyCalculatorImplementation;

class MyCalculator
{
public:
    MyCalculator();
    int CalculateStuff(int);

private:
    MyCalculatorImplementation* pimpl;
};

namespace // If i omit the namespace, everything is OK
{
    class MyCalculatorImplementation
    {
    public:
        int Calculate(int input)
        {
            // Insert some complicated calculation here
        }

    private:
        int state[100];
    };
}

// error C2872: 'MyCalculatorImplementation' : ambiguous symbol
MyCalculator::MyCalculator(): pimpl(new MyCalculatorImplementation)
{
}

int MyCalculator::CalculateStuff(int x)
{
    return pimpl->Calculate(x);
}

5 个答案:

答案 0 :(得分:6)

不,必须至少在使用指针类型之前声明类型,并且在标头中放置匿名命名空间将无法正常工作。但无论如何,你为什么要这样做呢?如果你真的想隐藏实现类,那就把它变成一个私有的内部类,即

// .hpp
struct Foo {
    Foo();
    // ...
private:
    struct FooImpl;
    boost::scoped_ptr<FooImpl> pimpl;
};

// .cpp
struct Foo::FooImpl {
    FooImpl();
    // ...
};

Foo::Foo() : pimpl(new FooImpl) { }

答案 1 :(得分:2)

是。有一个解决方案。将头文件中的指针声明为void *,然后在实现文件中使用重新解释转换。

注意:这是否是一个理想的解决方案是另一个问题。正如人们常说的那样,我将把它作为读者的练习。

请参阅下面的示例实现:

class MyCalculator 
{
public:
    MyCalculator();
    int CalculateStuff(int);

private:
    void* pimpl;
};

namespace // If i omit the namespace, everything is OK
{
    class MyCalculatorImplementation
    {
    public:
        int Calculate(int input)
        {
            // Insert some complicated calculation here
        }

    private:
        int state[100];
    };
}

MyCalculator::MyCalculator(): pimpl(new MyCalculatorImplementation)
{
}

MyCalaculator::~MyCalaculator() 
{
    // don't forget to cast back for destruction!
    delete reinterpret_cast<MyCalculatorImplementation*>(pimpl);
}

int MyCalculator::CalculateStuff(int x)
{
    return reinterpret_cast<MyCalculatorImplementation*>(pimpl)->Calculate(x);
}

答案 2 :(得分:1)

不,你不能这样做。你必须向前声明Pimpl类:

class MyCalculatorImplementation;

并宣布该课程。如果您随后将定义放入未命名的命名空间,则表示您正在创建另一个类(anonymous namespace)::MyCalculatorImplementation,该类与::MyCalculatorImplementation无关。

如果这是任何其他命名空间NS,您可以修改forward-declaration以包含命名空间:

namespace NS {
    class MyCalculatorImplementation;
}

但是未命名的命名空间,就像它一样神奇,当该标题包含在其他翻译单元中时,它将解析为其他标题(每当您将该标题包含到另一个翻译单元时,您将声明一个新类)。 / p>

但是这里不需要使用匿名命名空间:类声明可以是公共的,但是实现文件中的定义只对实现文件中的代码可见。

答案 3 :(得分:1)

如果您确实需要在头文件中使用前向声明的类名,并在模块文件中使用匿名命名空间中的实现,那么将声明的类作为接口:

// header
class MyCalculatorInterface;

class MyCalculator{
   ...
   MyCalculatorInterface* pimpl;
};



//module
class MyCalculatorInterface{
public:
    virtual int Calculate(int) = 0;
};

int MyCalculator::CalculateStuff(int x)
{
    return pimpl->Calculate(x);
}

namespace {
    class MyCalculatorImplementation: public MyCalculatorInterface {
        ...
    };
}

// Only the ctor needs to know about MyCalculatorImplementation
// in order to make a new one.
MyCalculator::MyCalculator(): pimpl(new MyCalculatorImplementation)
{
}

答案 4 :(得分:0)

markshiz 和 quamrana 为以下解决方案提供了灵感。

class Implementation 旨在在全局头文件中声明,并用作代码库中任何 pimpl 应用程序的 void*。它不在匿名/未命名的命名空间中,但由于它只有一个析构函数,因此命名空间污染仍然可以接受。

class MyCalculatorImplementation 派生自 class Implementation。由于 pimpl 被声明为 std::unique_ptr<Implementation>,因此无需在任何头文件中提及 MyCalculatorImplementation。所以现在 MyCalculatorImplementation 可以在匿名/未命名命名空间中实现。

好处是 MyCalculatorImplementation 中的所有成员定义都在匿名/未命名命名空间中。您必须付出的代价是,您必须将 Implementation 转换为 MyCalculatorImplementation。为此,提供了一个转换函数 toImpl()。 我怀疑是使用 dynamic_cast 还是 static_cast 进行转换。我猜 dynamic_cast 是典型的规定解决方案;但是 static_cast 也可以在这里工作,并且性能可能会更高一些。

#include <memory>

class Implementation
{
public:
  virtual ~Implementation() = 0;
};
inline Implementation::~Implementation() = default;

class MyCalculator
{
public:
  MyCalculator();
  int CalculateStuff(int);

private:
  std::unique_ptr<Implementation> pimpl;
};

namespace // Anonymous
{
class MyCalculatorImplementation
  : public Implementation
{
public:
  int Calculate(int input)
  {
    // Insert some complicated calculation here
  }

private:
  int state[100];
};

MyCalculatorImplementation& toImpl(Implementation& impl)
{
  return dynamic_cast<MyCalculatorImplementation&>(impl);
}
}

// no error C2872 anymore
MyCalculator::MyCalculator() : pimpl(std::make_unique<MyCalculatorImplementation>() )
{
}

int MyCalculator::CalculateStuff(int x)
{
  return toImpl(*pimpl).Calculate(x);
}