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

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);
}
5个回答

7
不,必须至少在指针类型使用之前声明类型,并且将匿名命名空间放在头文件中并不真正起作用。但是,无论如何,为什么您要这样做呢?如果您真的非常想隐藏实现类,请将其设置为私有内部类,即。
// .hpp
struct Foo {
    Foo();
    // ...
private:
    struct FooImpl;
    boost::scoped_ptr<FooImpl> pimpl;
};

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

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

1
这也是我用了最长时间的方法,直到有人指出,如果导出类 Foo,它还会导出类 Foo::FooImpl,而通常这不是你想要的... - Marc Mutz - mmutz
@mmutz,“_export_”是指与微软相关的__declspec(dllexport)吗?如果是,我可能不需要担心。 - anatolyg
@anatolyg:是的,在GCC/ELF系统上可以使用__attribute__((visibility=default)) - Marc Mutz - mmutz

3

是的,有一个解决方法。在头文件中将指针声明为void*,然后在实现文件中使用reinterpret cast。

注意:这是否是一个理想的解决方法是另一个问题。常常被说到,我会把这留给读者作为练习。

以下是一个示例实现:

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);
}

我认为如果reinterpret_cast确实是必要的话,传统的pimpl不会那么受欢迎。这显然不是正确的方法... - m8mble
@m8mble 为了明确,提出的问题并不是是否“可取”,而是是否“可能”。如上所述,尽管其他答案持相反意见,但肯定是可能的。 - markshiz
1
@m8mbl,你提出的问题是否必要是另一个完全不同的问题。因此,在这里进行负投票似乎有点过分了。该帖子仍然提供了有用的信息,并提供了一种可能对某人有用的解决方法。 - markshiz

1

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

class MyCalculatorImplementation;

这段代码声明了一个类。如果你将定义放入未命名的命名空间中,那么你就创建了另一个类 (anonymous namespace)::MyCalculatorImplementation,它与 ::MyCalculatorImplementation 没有任何关系。

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

namespace NS {
    class MyCalculatorImplementation;
}

但是未命名的命名空间非常神奇,当该头文件包含到其他翻译单元中时,它将解析为其他内容(每当您在另一个翻译单元中包含该头文件时,都会声明一个新类)。

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


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)
{
}

你仍然会在公共类的命名空间中污染Interface类 -> 没有收益。 - Marc Mutz - mmutz

0

以下解决方案的灵感来自于markshiz和quamrana。

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

MyCalculatorImplementation类派生自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);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接