2011-04-21 43 views
5

我正在尝试使用pimpl模式并在匿名命名空间中定义实现类。这在C++中可能吗?我的失败尝试如下所述。pimpl与匿名命名空间兼容吗?

有没有可能解决这个问题,而无需将实现移动到名称空间(或全局名称空间)?

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

回答

6

号,类型必须至少声明就可以使用指针类型之前,并把匿名命名空间中的标题将没有真正的工作。但是,无论如何,你为什么要这么做呢?如果你真的想要隐藏实现类,使其成为一个私人内部类,即

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

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

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

这是我使用时间最长,也是如此,直到有人向我指出,如果出口类'Foo',还出口类'美孚: :FooImpl',这通常不是你想要的... – 2011-04-21 14:56:39

+0

@mmutz是否_export_表示与MS有关的'__declspec(dllexport)'?如果是的话,我可能不需要担心。 – anatolyg 2011-04-21 15:01:45

+0

@anatolyg:是,或'__attribute __((visibility = default))'在GCC/ELF系统上。 – 2011-04-21 15:04:17

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

您仍然会使用'Interface'类污染公共类的命名空间 - >没有收获。 – 2011-04-21 17:44:48

2

是。有一个解决这个问题的方法。将头文件中的指针声明为void *,然后在实现文件中使用reinterpret强制转换。

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

请参见下面的实现:

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

我认为如果'reinterpret_cast'实际上是必要的,古典pimpl不会那么受欢迎。这显然不是要走的路...... – m8mble 2016-07-18 19:26:04

+0

@ m8mble要清楚,提出的问题不是它是否是_advisable_;问题是这是否是可能的。如上所述,尽管存在其他相反的答案,但这绝对是可能的。 – markshiz 2016-07-20 03:04:54

+1

@ m8mbl你是否需要的问题完全是另一个问题。所以这里的倒票似乎有点多。该帖子仍然是信息丰富的,并提供了一种可能对某人有用的解决方法。 – markshiz 2016-07-20 03:10:46