我正在测试一个C ++程序(演示装饰器设计模式),但发现了一个奇怪的问题。示例代码如下所示。错误行错误地有一个额外的new
表达式,但令人惊讶的是代码编译并运行错误输出(装饰器输出两次)。
$ ./a.out
simple window with scroll bar with scroll bar
这里发生了什么?
#include <iostream>
#include <string>
using namespace std;
class Window {
public:
virtual string desc() = 0;
virtual ~Window() {}
};
class SimpleWindow : public Window {
public:
string desc() { return "simple window"; }
};
class WindowDecorator : public Window {
protected:
Window *window;
public:
WindowDecorator(Window *window) : window(window) {}
};
class ScrollBar : public WindowDecorator {
public:
ScrollBar(Window *window) : WindowDecorator(window) {}
string desc() { return window->desc() + " with scroll bar"; }
};
int main()
{
ScrollBar scrollBar = new ScrollBar(new SimpleWindow()); // error line
cout << scrollBar.desc() << endl;
return 0;
}
答案 0 :(得分:3)
这里有一个例子,说明为什么我们在C ++中使用explicit
关键字作为构造函数。 (见cppreference.com)
你看,带有单个参数的构造函数,例如ScrollBar
类中的参数......
ScrollBar(Window *window) : WindowDecorator(window) {}
...除非标记为explicit
,编译器将使用它来执行隐式转换,在您的情况下,从Window *
到ScrollBar
。这样的构造函数也称为转换构造函数。
那么,这一行会发生什么......
ScrollBar scrollBar = new ScrollBar(new SimpleWindow());
...编译器愉快地(隐式地)再次调用ScrollBar(Window *window)
构造函数来转换ScrollBar *
,从new ScrollBar()
转到ScrollBar
。这有效,因为:
ScrollBar
是Window
的子类(通过WindowDecorator
),因此ScrollBar *
可以隐式转换为Window *
。ScrollBar(Window* window)
只有一个参数,不标记为explicit
,因此您从{{1}返回的指针中获取ScrollBar
的实例}。这很可能不是你在编写构造函数时想到的,所以你应该把它重写为:
new ScrollBar()
这会导致令人惊讶的行导致编译错误。
在C ++中,默认情况下始终使用explicit ScrollBar(Window *window) : WindowDecorator(window) {}
作为单参数构造函数通常是一个好主意,只有当您有意决定允许隐式转换语义时才将其删除。
答案 1 :(得分:1)
根据C ++标准(4.10指针转换)
3类型为“指向cv D的指针”的prvalue,其中D是类类型,可以是 转换为“指向cv B指针”类型的prvalue,其中B是基数 D ......的阶级(第10条)。
和(4次标准转换)
1标准转化为内置的隐式转化 含义。第4条列举了全套此类转换。一个 标准转换序列是一系列标准转换 以下顺序:
...
- 来自以下设置的零次或一次转化:整体促销, 浮点数提升,积分转换,浮点数 转换,浮点积分转换,指针转换, 指向成员转换的指针和布尔转换。
在本声明中
ScrollBar scrollBar = new ScrollBar(new SimpleWindow());
在右侧有一个派生类ScrollBar
的指针,可以将其转换为基类Window
的指针。
班级ScrollBar
有转换构造函数
ScrollBar(Window *window) : WindowDecorator(window) {}
在上面的陈述中调用。