如何在使用pimpl习语

时间:2017-08-05 18:57:38

标签: c++ c++11 pimpl-idiom

背景

我一直在学习如何使用Herb Sutter在此页面描述的较新的c ++ 11方法实现pimpl习语:https://herbsutter.com/gotw/_100/

我正在尝试通过向私有实现添加成员变量来修改此示例,特别是std :: string(尽管char *具有相同的问题)。

问题

由于使用了静态const非整数类型,这似乎是不可能的。类内初始化只能对整数类型进行,但因为它是静态的,所以它也不能在构造函数中初始化。

此问题的解决方案是在头文件中声明私有变量,并在实现中初始化它,如下所示: C++ static constant string (class member)

然而,这个解决方案对我不起作用,因为它破坏了我试图通过pimpl习语实现的封装。

问题

如何在使用pimpl习语时隐藏隐藏内部类中的非整数静态const变量?

实施例

以下是我可以提出的最简单(不正确)示例:

Widget.h:

#ifndef WIDGET_H_
#define WIDGET_H_

#include <memory>

class Widget {
public:
    Widget();
    ~Widget();
private:
    class Impl;
    std::unique_ptr<Impl> pimpl;
};

#endif

Widget.cpp:

#include "Widget.h"
#include <string>

class Widget::Impl {
public:
    static const std::string TEST = "test";

    Impl() { };
    ~Impl() { };
};

Widget::Widget() : pimpl(new Impl()) { }
Widget::~Widget() { }

编译命令:

g++ -std=c++11 -Wall -c -o Widget.o ./Widget.cpp

请注意,此示例无法编译,因为变量TEST在声明时无法分配,因为它不是整数类型;但是,因为它是静态的,所以这是必需的。这似乎意味着无法做到。

我整个下午一直在寻找以前的问题/答案,但找不到任何提出保留pimpl习语的信息隐藏属性的解决方案。

解决方案观察:

在上面的例子中,我试图在Impl类声明中分配TEST的值,该声明位于Widget.cpp中,而不是它自己的头文件中。 Impl的定义也包含在Widget.cpp中,我相信这是我混淆的原因。

只需在Impl声明之外移动TEST的赋值(但仍在Widget / Impl定义中),问题似乎就解决了。

在下面的两个示例解决方案中,可以使用

从Widget中访问TEST

pimpl->TEST

尝试将不同的字符串分配给TEST,即

pimpl->TEST = "changed"

导致编译器错误(应该如此)。此外,尝试从Widget外部访问pimpl-&gt; TEST也会导致编译器错误,因为pimpl被声明为Widget的私有。

所以现在TEST是一个常量字符串,只能由Widget访问,不在公共头中命名,并且Widget的所有实例之间共享一个副本,完全符合要求。

解决方案示例(char *):

在使用char *的情况下,请注意添加另一个const关键字;这是防止将TEST更改为指向另一个字符串文字所必需的。

Widget.cpp:

#include "Widget.h"
#include <stdio.h>

class Widget::Impl {
public:
    static const char *const TEST;

    Impl() { };
    ~Impl() { };
};

const char *const (Widget::Impl::TEST) = "test";

Widget::Widget() : pimpl(new Widget::Impl()) { }
Widget::~Widget() { }

解决方案示例(字符串):

Widget.cpp:

#include "Widget.h"
#include <string>

class Widget::Impl {
public:
    static const std::string TEST;

    Impl() { };
    ~Impl() { };
};

const std::string Widget::Impl::TEST = "test";

Widget::Widget() : pimpl(new Widget::Impl()) { }
Widget::~Widget() { }

更新

我现在意识到这个问题的解决方案与pimpl习惯完全无关,而且只是定义静态常量的标准C ++方式。我已经习惯了其他语言,比如Java,其中常量必须在声明时定义,所以我对C ++的经验不足使我无法实现这一点。我希望这可以避免对这两个主题产生任何混淆。

2 个答案:

答案 0 :(得分:3)

#include <memory>

class Widget {
public:
    Widget();
    ~Widget();
private:
    class Impl;
    std::unique_ptr<Impl> pimpl;
};

/*** cpp ***/

#include <string>

class Widget::Impl {
public:
    static const std::string TEST;

    Impl() { };
    ~Impl() { };
};

const std::string Widget::Impl::TEST = "test"; 

Widget::Widget() : pimpl(new Impl()) { }
Widget::~Widget() { }

您可能需要考虑将TEST作为返回const std::string&的静态函数。这将允许您内联定义。

答案 1 :(得分:1)

您也可以在示例中将const替换为constexpr,然后进行编译。

class Widget::Impl {
public:
    static constexpr std::string TEST = "test";  // constexpr here

    Impl() { };
    ~Impl() { };
};

<强>更新

好吧,似乎我错了......当我想要常量时,我​​总是存储原始字符串。

class Widget::Impl {
public:
    static constexpr char * const TEST = "test";
};

根据使用模式,可能适合与否。如果没有,则按照另一个答案中的说明定义变量。