6

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

5 回答 5

7

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

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

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

Foo::Foo() : pimpl(new FooImpl) { }
于 2011-04-21T14:50:59.883 回答
3

是的。有一个解决方法。将头文件中的指针声明为 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);
}
于 2011-07-11T14:54:44.600 回答
1

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

class MyCalculatorImplementation;

并声明了类。如果您随后将定义放入未命名的命名空间中,那么您将创建另一个与 .(anonymous namespace)::MyCalculatorImplementation无关的类::MyCalculatorImplementation

如果这是任何其他命名空间NS,您可以修改前向声明以包含命名空间:

namespace NS {
    class MyCalculatorImplementation;
}

但是未命名的命名空间虽然很神奇,但当该标头包含在其他翻译单元中时,它将解析为其他内容(只要将该标头包含到另一个翻译单元中,您就会声明一个新类)。

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

于 2011-04-21T14:55:16.793 回答
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)
{
}
于 2011-04-21T15:26:46.997 回答
0

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

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

class MyCalculatorImplementation源自class Implementation。因为pimpl被声明为std::unique_ptr<Implementation>不需要MyCalculatorImplementation在任何头文件中提及。所以现在MyCalculatorImplementation可以在匿名/未命名的命名空间中实现。

好处是所有成员定义MyCalculatorImplementation都在匿名/未命名的命名空间中。您必须付出的代价是您必须转换ImplementationMyCalculatorImplementation. 为此目的,toImpl()提供了转换功能。我在怀疑是否使用 adynamic_cast或 astatic_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);
}
于 2021-03-24T10:50:05.783 回答