最近写设计模式作业的时候, 有一个作业是实现装饰器模式 (Decorator Pattern), 由于我不会 Java, 所以只能用 C++ 来实现

在这个背景下, 会有简单(表意)的几个类, 如下:

class Base
{
public:
virtual ~Base() = 0;
virtual int getData() const = 0;
}; inline Base::~Base() {} class DerivedA final : public Base
{
public:
DerivedA(int data) : data_(data) {}
int getData () const override
{
return data_;
} private:
const int data_;
}; class DerivedB final : public Base
{
public:
DerivedB(const Base &pre) : pre_(pre) {}
int getData () const override
{
return pre_.getData() + 1;
} private:
const Base &pre_;
};

简单来写就是上面这样, DerivedB 类型的对象可以接收以 Base 类作为基类的对象引用并且绑定到成员 pre_ 上, 在调用 getData 方法时会调用 pre_ 绑定的对象的 getData 方法, 并在其结果的基础上运算后返回

而这样的设计会导致一种很直觉的使用方法, 如下:

cout << DerivedB(DerivedB(DerivedA(10))).getData() << endl;

也即嵌套对象, 实现 getData 的多次调用

但是这样的使用方式会造成与预期不符的结果出现, 如下:

~> g++ -std=c++17 test.cpp -o test
~> ./test
11
~> clang++ -std=c++17 test.cpp -o test
~> ./test
11

会发现结果表明, 只有一个 DerivedB 类型的对象被构造了出来, cppreference 的 copy elision 章节中的解释部分有提到:

Under the following circumstances, the compilers are required to omit the copy and move construction of class objects, even if the copy/move constructor and the destructor have observable side-effects. The objects are constructed directly into the storage where they would otherwise be copied/moved to. The copy/move constructors need not be present or accessible, as the language rules ensure that no copy/move operation takes place, even conceptually:

  • In a return statement, when the operand is a prvalue of the same class type (ignoring cv-qualification) as the function return type:
T f() {
return T();
} f(); // only one call to default constructor of T
  • In the initialization of a variable, when the initializer expression is a prvalue of the same class type (ignoring cv-qualification) as the variable type:
T x = T(T(f())); // only one call to default constructor of T, to initialize x

上面这句被我标粗的文字可以看到, 即使拷贝/移动构造有副作用, 依然只构造一次, 甚至不需要有拷贝/移动构造函数

可以在类中添加如下定义:

class DerivedB final : public Base
{
public:
// new additions
DerivedB(const DerivedB &) = delete;
DerivedB(DerivedB &&) = delete; DerivedB(const Base &pre) : pre_(pre) {}
int getData () const override
{
return pre_.getData() + 1;
} private:
const Base &pre_;
};

会发现依然可以通过编译并且运行结果与之前相同 ( 因为在 C++17 中 Copy Elision 已经不再是可选项 ), 但是在 C++17 之前如果 delete 了这两个拷贝/移动构造函数, 会导致无法通过编译, 尽管有可以匹配 const Base & 类型的构造函数, 也依然不可以:

test.cpp:45:13: error: functional-style cast from 'DerivedB'
to 'DerivedB' uses deleted function
cout << DerivedB(DerivedB(DerivedA(10))).getData...
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
test.cpp:31:5: note: candidate constructor has been
explicitly deleted
DerivedB(DerivedB &&) = delete;
^
test.cpp:30:5: note: candidate constructor has been
explicitly deleted
DerivedB(const DerivedB &) = delete;
^
test.cpp:33:5: note: candidate constructor
DerivedB(const Base &pre) : pre_(pre) {}
^
1 error generated.

感谢 禽牙 在评论中提出, 事实上如果我们在尚未 delete 那两个构造函数通过如下的方式调用, 也依然不可行:

DerivedA a(10);
DerivedB b1(a);
DerivedB b2(b1);
cout << b2.getData() << endl;

结果依然是 11, 这是因为重载决议后其实我们调用的是匹配类型为 const DerivedB & 的构造函数, 同样如果我们约定在代码中都是用这样的方式编写程序, 我们就可以获得一种解决方式, 编写匹配类型的构造函数, 如下:

...
DerivedB(const DerivedB &pre) : pre_(pre) {}
...

再通过声明中间变量的方式调用就可以获得正确结果, 但是通过纯右值的形式依然不行:

...
DerivedA a(10);
DerivedB b1(a);
DerivedB b2(b1);
cout << b2.getData() << endl;
// print 12
...
cout << DerivedB(DerivedB(DerivedA(10))).getData() << endl;
// print 11
...

所以如何才能在这种设计下通过这种方式正常使用呢?

一种可以显著增加代码程度的方式是, 手动添加强制转换:

cout << DerivedB(static_cast<const Base &>(DerivedB(DerivedA(10)))).getData() << endl;
cout << DerivedB((const Base &)(DerivedB(DerivedA(10)))).getData() << endl; // C-style

当然这种方式其实还是可以接受的

还有一种可以不会增加太多冗余代码的方式是在构造函数里增加一个冗余参数, 区分开就可以了, 比如:

...
class DerivedB final : public Base
{
public:
DerivedB(const Base &pre, int) : pre_(pre) {}
int getData () const override
{
return pre_.getData() + 1;
} private:
const Base &pre_;
};
...
cout << DerivedB(DerivedB(DerivedA(10), 0), 0).getData() << endl;

当然以上两种都是在不涉及模板的情况下完成的, 但是都会对调用的方便性产生影响

还有一种不会更改调用方式, 通过模板区分嵌套的相同类型的方式, 感谢 91khr 提供:

首先先将 DerivedB 变成一个类模板, 之后添加模板推导指引来实现嵌套区分

...
template <int Lv>
class DerivedB;
...
template <typename Ty> DerivedB(Ty) -> DerivedB<0>;
template <int Lv> DerivedB(DerivedB<Lv>) -> DerivedB<Lv + 1>;
...
cout << DerivedB(DerivedB(DerivedA(10))).getData() << endl;

在此条件之下, 结果与预期相同, 完美解决问题:

~> clang++ -std=c++17 test.cpp -o test
~> ./test
12

最新文章

  1. VS2013编译google protobuf 出现问题error C3861: “min”:
  2. java -cp
  3. Android系统手机端抓包方法
  4. PhpStorm中字体大小的改变方法
  5. php面试题之四——PHP面向对象(基础部分)
  6. ubuntu sublime-text package control
  7. Linux中的文件特殊权限
  8. 关于properties文件在项目中的使用
  9. Calendar.getInstance()获取当天指定点上的时间[转载]
  10. POJ 2127 最长公共上升子序列
  11. git 远程
  12. Function program language
  13. kubernetes学习笔记之十三:基于calico的网络策略入门
  14. Python高级网络编程系列之第二篇
  15. PPTP 在Centos 7.1 建立方法与失效处理
  16. 菜鸟学数据库(五)——MySQL必备命令
  17. 自定义容器启动脚本报错:exec user process caused &quot;no such file or directory&quot;
  18. [UI] 精美UI界面欣赏[8]
  19. 第十次作业psp
  20. XLua系统学习

热门文章

  1. spring security 继承 WebSecurityConfigurerAdapter 的重写方法configure() 参数 HttpSecurity 常用方法及说明
  2. mysql 外连接
  3. 利用代码生成工具Database2Sharp生成ABP VNext框架项目代码
  4. Guava Cache源码浅析
  5. 如何管理leader对你的能力预期?
  6. [STM32F10x] 利用定时器测量脉冲宽度
  7. Centos上通过yum命令删除有关MySQL
  8. MVC框架---转
  9. X架构键盘选购指南
  10. 关于网页中鼠标动作 onfocus onblur focus()