C++ c++;显式说明符生成更安全的代码
我一直在研究显式说明符,我希望我能在以下方面得到一些反馈(小示例会很完美):C++ c++;显式说明符生成更安全的代码,c++,c++11,C++,C++11,我一直在研究显式说明符,我希望我能在以下方面得到一些反馈(小示例会很完美): 显式构造函数如何防止复制初始化 您能否提供一个小例子,说明显式构造函数在生成更安全的代码方面比隐式构造函数有多大的好处 我不太关心显式转换函数(C++11),只关心一般的转换原理 如有任何反馈,将不胜感激 提前感谢1的示例。: struct Foo{}; struct Bar { explicit Bar(Foo){} }; int main() { Bar a {Foo{}}; // com
提前感谢
1的示例。:
struct Foo{};
struct Bar {
explicit Bar(Foo){}
};
int main() {
Bar a {Foo{}}; // compiles
Bar b = Foo{}; // error
}
a
编译,因为它使用直接初始化,所以可以使用显式构造函数<代码>b
未编译,因为它使用复制初始化
显式构造函数可防止意外执行隐式转换:
void oops (const Bar&) {
}
int main() {
oops (Foo{});
}
这将从传入的
Foo
构造一个临时条
,并将其绑定到引用参数。这对用户来说几乎是隐藏的,如果构造成本很高,或者您希望参数和参数是同一个对象,则可能会导致令人惊讶的结果。显式构造函数可以保护您避免此问题。关于1的示例。
:
struct Foo{};
struct Bar {
explicit Bar(Foo){}
};
int main() {
Bar a {Foo{}}; // compiles
Bar b = Foo{}; // error
}
a
编译,因为它使用直接初始化,所以可以使用显式构造函数<代码>b未编译,因为它使用复制初始化
显式构造函数可防止意外执行隐式转换:
void oops (const Bar&) {
}
int main() {
oops (Foo{});
}
这将从传入的Foo
构造一个临时条
,并将其绑定到引用参数。这对用户来说几乎是隐藏的,如果构造成本很高,或者您希望参数和参数是同一个对象,则可能会导致令人惊讶的结果。显式构造函数保护您免受此问题的影响。来自:
复制初始化的权限小于直接初始化:显式
构造函数不转换构造函数,也不考虑复制初始化
所以你第一个问题的答案是“设计”。复制初始化只考虑隐式转换
至于为什么要禁用隐式转换:当构造对象可能很危险时,它被用作安全措施,因此应该是显式的(在代码中可见)
例如,让我们设想std::unique\u ptr
中来自T*
的构造函数是隐式的。现在让我们看看一些(人为的)用户代码:
void forbulate(Foo * const myFoo) {
myFoo->doStuff();
lookAt(myFoo); // My Foo is amazing
myFoo->doMoreStuff();
}
乍一看这个很好。但你不知道的是,lookAt
实际上有以下原型:
void lookAt(std::unique_ptr<Foo> yourFoo);
请注意,删除本身可能已经是UB(例如,如果myFoo
是自动实例的地址)。无论如何,这个看起来无害的代码实际上是一颗地雷。没有到处散布地雷是我们拥有显式构造函数的原因:使用实际的独特的\u ptr
规范,代码甚至不编译,您知道哪里出了问题。来自:
复制初始化的权限小于直接初始化:显式
构造函数不转换构造函数,也不考虑复制初始化
所以你第一个问题的答案是“设计”。复制初始化只考虑隐式转换
至于为什么要禁用隐式转换:当构造对象可能很危险时,它被用作安全措施,因此应该是显式的(在代码中可见)
例如,让我们设想std::unique\u ptr
中来自T*
的构造函数是隐式的。现在让我们看看一些(人为的)用户代码:
void forbulate(Foo * const myFoo) {
myFoo->doStuff();
lookAt(myFoo); // My Foo is amazing
myFoo->doMoreStuff();
}
乍一看这个很好。但你不知道的是,lookAt
实际上有以下原型:
void lookAt(std::unique_ptr<Foo> yourFoo);
请注意,删除本身可能已经是UB(例如,如果myFoo
是自动实例的地址)。无论如何,这个看起来无害的代码实际上是一颗地雷。没有到处撒布地雷是我们拥有显式构造函数的原因:使用实际的独特的\u ptr
规范,代码甚至没有编译,你知道哪里出了问题。我的示例:
class MyClass {
public:
MyClass() = default;
// copy constructor
explicit MyClass(const MyClass& other) = default;
// conversion constructor
explicit MyClass(int val) : val_(val) {}; // constructor that creates a MyClass object from an int.. in other words this 'converts' an int into a MyClass object
private:
int val_;
};
void foo(MyClass n) {};
int main() {
MyClass obj;
MyClass obj2 = obj; // implicit use of the copy constructor - does not compile if the copy ctor is marked explicit
MyClass obj3(obj); // explicit call to the copy ctr... always works
foo(obj); // regular call to foo, copy ctor called
foo(2); // implicit use of the conversion ctr - does not compile if the conversion ctor is marked explicit.
// this automatic conversion could be risky/undesired in some cases.. therefore the need for the explicit keyword.
return 0;
}
我的例子是:
class MyClass {
public:
MyClass() = default;
// copy constructor
explicit MyClass(const MyClass& other) = default;
// conversion constructor
explicit MyClass(int val) : val_(val) {}; // constructor that creates a MyClass object from an int.. in other words this 'converts' an int into a MyClass object
private:
int val_;
};
void foo(MyClass n) {};
int main() {
MyClass obj;
MyClass obj2 = obj; // implicit use of the copy constructor - does not compile if the copy ctor is marked explicit
MyClass obj3(obj); // explicit call to the copy ctr... always works
foo(obj); // regular call to foo, copy ctor called
foo(2); // implicit use of the conversion ctr - does not compile if the conversion ctor is marked explicit.
// this automatic conversion could be risky/undesired in some cases.. therefore the need for the explicit keyword.
return 0;
}
explicit
不阻止复制构造(或构造形式不同的类型,也称为转换构造),但它可以防止这种情况隐式发生。explicit
的可能重复不会阻止复制构造(或构造形式不同的类型,称为转换构造),但是它可以防止这种情况的发生。可能是重复的谢谢你。你能详细说明这个例子吗?使用值来说明问题是如何产生的?@Spirit我不太清楚你在问什么。对不起,只是想知道是否有可能显示隐式构造函数如何以示例的形式产生不需要的值?该示例显示了隐式转换。将从传入的Foo
构建一个临时条
,这在调用站点中并不明显。如果构造函数是显式的,那么您需要编写oops(Bar{Foo{}}})
,这清楚地表明转换是您想要的。谢谢您,您能用值详细说明示例以说明如何出现问题吗?@Spirit我不太清楚您的要求。对不起,只是想知道是否有可能显示隐式构造函数如何以示例的形式产生不需要的值?该示例显示了隐式转换。将从传入的Foo
构建一个临时条
,这在调用站点中并不明显。如果构造函数是显式的,那么您需要编写oops(Bar{Foo{}}})
,这表明转换是您想要的。