pimpl 与匿名命名空间兼容吗?

发布于 2024-11-02 20:53:22 字数 803 浏览 2 评论 0原文

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

I am trying to use the pimpl pattern and define the implementation class in an anonymous namespace. Is this possible in C++? My failed attempt is described below.

Is it possible to fix this without moving the implementation into a namespace with a name (or the global one)?

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

潦草背影 2024-11-09 20:53:22

不,在使用指针类型之前至少必须声明该类型,并且将匿名名称空间放入标头中实际上不起作用。但你为什么要这么做呢?如果你真的想隐藏实现类,请将其设为私有内部类,即

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

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

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

No, the type must be at least declared before the pointer type can be used, and putting anonymous namespace in the header won't really work. But why would you want to do that, anyway? If you really really want to hide the implementation class, make it a private inner class, i.e.

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

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

Foo::Foo() : pimpl(new FooImpl) { }
↘人皮目录ツ 2024-11-09 20:53:22

是的。有一个解决方法可以解决这个问题。将头文件中的指针声明为 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);
}

Yes. There is a work around for this. Declare the pointer in the header file as void*, then use a reinterpret cast inside your implementation file.

Note: Whether this is a desirable work-around is another question altogether. As is often said, I will leave that as an exercise for the reader.

See a sample implementation below:

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);
}
╰つ倒转 2024-11-09 20:53:22

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

class MyCalculatorImplementation;

并声明该类。如果您随后将定义放入未命名的命名空间中,则会创建另一个类 (匿名命名空间)::MyCalculatorImplementation,它与 ::MyCalculatorImplementation 无关。

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

namespace NS {
    class MyCalculatorImplementation;
}

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

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

No, you can't do that. You have to forward-declare the Pimpl class:

class MyCalculatorImplementation;

and that declares the class. If you then put the definition into the unnamed namespace, you are creating another class (anonymous namespace)::MyCalculatorImplementation, which has nothing to do with ::MyCalculatorImplementation.

If this was any other namespace NS, you could amend the forward-declaration to include the namespace:

namespace NS {
    class MyCalculatorImplementation;
}

but the unnamed namespace, being as magic as it is, will resolve to something else when that header is included into other translation units (you'd be declaring a new class whenever you include that header into another translation unit).

But use of the anonymous namespace is not needed here: the class declaration may be public, but the definition, being in the implementation file, is only visible to code in the implementation file.

一花一树开 2024-11-09 20:53:22

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

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

If you actually want a forward declared class name in your header file and the implementation in an anonymous namespace in the module file, then make the declared class an interface:

// 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)
{
}
春风十里 2024-11-09 20:53:22

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

类实现,旨在在全局头文件中声明,并充当代码库中任何 pimpl 应用程序的 void*。它不在匿名/未命名的命名空间中,但由于它只有析构函数,因此命名空间污染仍然在可接受的范围内。

MyCalculatorImplementation 类 派生自Implementation 类。由于 pimpl 被声明为 std::unique_ptr,因此无需在任何头文件中提及 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);
}

markshiz and quamrana provided the inspiration for the solution below.

class Implementation, is intended to be declared in a global header file and serves as a void* for any pimpl application in your code base. It is not in an anonymous/unnamed namespace, but since it only has a destructor the namespace pollution remains acceptably limited.

class MyCalculatorImplementation derives from class Implementation. Because pimpl is declared as std::unique_ptr<Implementation> there is no need to mention MyCalculatorImplementation in any header file. So now MyCalculatorImplementation can be implemented in an anonymous/unnamed namespace.

The gain is that all member definitions in MyCalculatorImplementation are in the anonymous/unnamed namespace. The price you have to pay, is that you must convert Implementation to MyCalculatorImplementation. For that purpose a conversion function toImpl() is provided.
I was doubting whether to use a dynamic_cast or a static_cast for the conversion. I guess the dynamic_cast is the typical prescribed solution; but static_cast will work here as well and is possibly a little more performant.

#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);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文