如何从类定义中省略私有非虚方法?

时间:2011-04-04 12:03:14

标签: c++ c++11 private forward-declaration pimpl-idiom

假设我有以下内容:

a.hpp:

class B;

class A
{
private:
  std::unique_ptr<B> b_;
}

a.cpp:

#include <something_complicated.hpp>

struct B
{
  something_complicated x;
}

something_complicated& A::get_complicated() { return b_->x; }

不幸的是,在这种情况下,a.cp​​p将会编译,因为“get_complicated()”不是A的方法。

所以,我们可以试试这个:

a.hpp:

class B;

class A
{
private:
  std::unique_ptr<B> b_;
  something_complicated& A::get_complicated();
}

但是a.hpp无法编译,因为没有定义something_complicated。

如果它是一个类,我们可以转发声明something_complicated,但它可能是一个typedef,所以这就是out。

我能想到这样做的唯一方法是在不使用b_ public但在a.hpp中包含something_complicated.hpp的情况下如下:

a.cpp:

#include <something_complicated.hpp>

struct B
{
  something_complicated x;
}

#define get_complicated ((b_->x))

当然,我不必定义一个宏来解决这个问题?任何替代方案?

6 个答案:

答案 0 :(得分:2)

最简单的解决方案可能是在类中包含对复杂类型的引用,在a.hpp中向前声明,并在something_complicated.hpp中定义。

a.hpp:

class B;
class complicated_ref;

class A
{
public:
  complicated_ref get_complicated();
private:
  std::unique_ptr<B> b_;
};

something_complicated.hpp:

// ... complicated definitions ...
typedef whatever something_complicated;

struct complicated_ref
{
    complicated_ref(something_complicated & thing) : ref(thing) {}
    something_complicated & ref;
};

现在a.cpp并且任何需要使用复杂类型的内容都必须包含它的标题,但是任何只想使用class A的内容都不需要。

这假设A的某些客户有充分理由访问复杂的内容,但是B无法访问每个人。在需要时允许访问B会更简单,并通过它来解决复杂问题。

答案 1 :(得分:1)

请勿在{{1​​}}中提及something_complicated

一种解决方案是使用自由函数或另一个类的静态方法替换成员函数a.hpp

<强>·H

get_complicated

<强>的.cpp

class A_impl_base {
    A_impl_base() {}

    friend class A_impl; // all instances of A_impl_base are A_impl
}; // this stub class is the only wart the user sees

class A
{
private:
    std::unique_ptr< A_impl_base > b_; // this is not a wart, it's a pimpl

    friend class A_impl;
}

答案 2 :(得分:1)

出了什么问题:

<强> a.hpp:

class B;

class A
{
private:
  std::unique_ptr<B> b_;
public:
  B& get_B();
}

如果您的客户希望从B中获得复杂的内容,请让他们#include <something_complicated.hpp>

答案 3 :(得分:1)

我担心对于什么属于班级有什么误解,什么没有。

并非所有作用于类内部的方法都应该是类方法,毕竟我们已经有friend个函数。我知道很多人将帮助方法声明为私有函数,但这样做会引入不必要的依赖(编译时)和friend的可见性问题。

在处理PIMPL时,我倾向于不使用私有函数。相反,选择是:

  • 使Impl(在您的情况下为B)成为一个真正的类,具有自己的验证逻辑和真正的API
  • 使用static个免费函数(或匿名命名空间中声明的函数)

两者都很好,并使用最合适的。即:

  • 方法:处理验证问题时
  • 自由功能:用于可以用上述方法表达的计算

我有意识地搜索尽可能少的方法,因为那些方法是唯一可以搞砸我的类不变量的方法,并且它们越少,我就越有信心保持不变量

在您的情况下,由您决定哪种方法最适合您。

在行动中:

<强> a.cpp

#include <something_complicated.hpp>

struct B
{
  something_complicated x;
}

static something_complicated& get_complicated(B& b) { return b_.x; }

// or anonymous namespace instead
namespace {
  something_complicated& get_complicated(B& b) { return b_.x; }
}

与你的不一样,是吗?

注意:我更喜欢静态函数到匿名命名空间,因为它在阅读时更为明显。命名空间引入了范围,在筛选文件时不会轻易查看范围。您的里程可能会有所不同,两者都提供相同的功能(功能)。

答案 4 :(得分:0)

  

如果它是一个类,我们可以转发声明something_complicated,但它可能是一个typedef,所以这就是out。

这正是你必须要做的。而且我没有看到typedef如何排除前瞻声明。

答案 5 :(得分:0)

如果您控制something_complicated.hpp,您可以执行标准库的操作:创建一个具有适当前向声明的something_complicated_fwd.hpp,包括可能是也可能不是typedef的类型。