我正在尝试使用 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);
}
答案 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);
}