C++ 为什么默认构造函数和具有2个或更多(非默认)参数的构造函数允许显式?

C++ 为什么默认构造函数和具有2个或更多(非默认)参数的构造函数允许显式?,c++,explicit-constructor,C++,Explicit Constructor,我知道,具有一个(非默认)参数的构造函数的行为类似于隐式转换器,它从该参数类型转换为类类型。但是,explicit可用于限定任何构造函数,包括没有参数的构造函数(默认构造函数)或具有2个或更多(非默认)参数的构造函数 为什么在这些构造函数上允许显式?有没有这样的例子可以用来防止某种隐式转换?这可能只是一种方便;没有理由不允许这样做,那么为什么要让代码生成器的生活变得困难呢?如果选中,则代码生成例程必须有一个额外的步骤来验证生成的构造函数有多少个参数 根据,当应用于不能用一个参数调用的构造函数时,

我知道,具有一个(非默认)参数的构造函数的行为类似于隐式转换器,它从该参数类型转换为类类型。但是,
explicit
可用于限定任何构造函数,包括没有参数的构造函数(默认构造函数)或具有2个或更多(非默认)参数的构造函数


为什么在这些构造函数上允许显式?有没有这样的例子可以用来防止某种隐式转换?

这可能只是一种方便;没有理由不允许这样做,那么为什么要让代码生成器的生活变得困难呢?如果选中,则代码生成例程必须有一个额外的步骤来验证生成的构造函数有多少个参数


根据,当应用于不能用一个参数调用的构造函数时,它没有任何效果。

也许它是为了支持维护。通过对多参数构造函数使用
explicit
,可以避免在向参数添加默认值时无意中引入隐式转换。虽然我不相信;相反,我认为,C++中有很多事情是不允许语言定义比它更复杂的。 也许最臭名昭著的情况是返回对非静态局部变量的引用。它需要额外的复杂规则来排除所有“无意义”的事情,而不影响其他任何事情。所以这是允许的,如果你使用那个引用

或者对于构造函数,您可以定义任意数量的默认构造函数,只要它们的签名不同,但如果有多个,则很难在默认情况下调用其中任何一个构造函数。:-)

一个更好的问题可能是,为什么转换运算符上也不允许使用
显式

在C++0x中,它将是。所以没有理由不这样做。不允许转换运营商使用
explicit
的实际原因可能与监督一样平淡无奇,或者首先是为了让
explicit
获得通过而进行的斗争,或者委员会时间的简单优先顺序,或者其他任何事情


欢呼和HTH,

< P>根据<强>高完整性C++编码标准,您应该声明所有SnLGGE参数构造函数为<强>显式< /强>,以避免在类型转换中附带使用。在这种情况下,它是一个多参数构造函数,假设您有一个接受多个参数的构造函数,每个参数都有一个默认值,以某种默认构造函数和转换构造函数转换构造函数:

class C { 
    public: 
    C( const C& );   // ok copy 
    constructor C(); // ok default constructor 
    C( int, int ); // ok more than one non-default argument 

    explicit C( int ); // prefer 
    C( double ); // avoid 
    C( float f, int i=0 ); // avoid, implicit conversion constructor 
    C( int i=0, float f=0.0 ); // avoid, default constructor, but 
                               // also a conversion constructor 
}; 
void bar( C const & ); 
void foo() 
{ 
    bar( 10 );  // compile error must be 'bar( C( 10 ) )' 
    bar( 0.0 ); // implicit conversion to C 
}

一个原因当然是因为它不疼

需要它的一个原因是,如果第一个参数有默认参数。构造函数成为默认构造函数,但仍可以用作转换构造函数

struct A {
  explicit A(int = 0); // added it to a default constructor
};
C++0x将其实际用于多参数构造函数。在C++0x中,可以使用初始值设定项列表初始化类对象。哲学是

  • 如果使用
    ={…}
    ,则使用一种“复合值”初始化对象,该值在概念上表示对象的抽象值,并且要转换为类型

  • 如果使用
    {…}
    初始值设定项,则直接调用对象的构造函数,而不一定要指定转换

以这个例子为例

struct String {
    // this is a non-converting constructor
    explicit String(int initialLength, int capacity);
};

struct Address {
    // converting constructor
    Address(string name, string street, string city);
};

String s = { 10, 15 }; // error!
String s1{10, 15}; // fine

Address a = { "litb", "nerdsway", "frankfurt" }; // fine

通过这种方式,C++0x表明,C++03决定允许在其他构造函数上显式,这一点都不是一个坏主意

显式使用默认构造函数的一个原因是,当存在到
class\U t::operator=
的重载,该重载接受类型为
U
std::is_same\U v==false的对象时,避免在赋值的右侧进行容易出错的隐式转换。像
class\U t\U instance={}
这样的赋值可能会导致我们产生不希望的结果,例如,如果我们有一个
observable
,它将move赋值操作符重载为类似
observable::operator=(U&)
,而
U
应该可以转换为
t
。令人困惑的赋值可以用默认构造的
T
(观察类型对象)的赋值来编写,但实际上程序员正在“擦除”可观察的
,因为如果默认构造函数是隐式的,则此赋值与
class\u T\u instance=class\u T\u instance{}
相同。看一看
可观察的
的玩具实现:

#包括
#包括
#包括
#包括
模板
可观测结构{
使用观察到的_t=t;
//使用隐式默认构造函数,我们可以指定`{}`
//显式版本“可观察{}”,但我认为这是
//一个容易出错的任务,因为程序员可以相信
//他/她正在定义默认值
//`可观察的,可观察的但实际上是左手边
//可观察的将被“擦除”,这意味着所有观察者都将
//被移除。
显式可观察()=默认值;
显式可观测(观测的):观测的(std::move(o)){
可观察的(可观察和与rhs)=默认值;
可观察和运算符=(可观察和rhs)=默认值;
模板
std::如果启用,则启用<
!std::是相同的吗,
可观察的&>
运算符=(U&&rhs){
_观察到的=标准:正向(rhs);
_改变后(观察到);
归还*这个;
}
模板
更改后自动(F&&F)
{change.connect(std::forward(f));}
const observed_t&observed()const noexcept
{返回_已观察到;}
私人:
观察到的\u t\u观察到的;
升压::信号2::改变后的信号;
};
int main(){
可观测的o;
o、 更改后([](自动v){std::cout falmari:如果是
#include <boost/signals2/signal.hpp>
#include <iostream>
#include <type_traits>
#include <utility>
 
template<typename T>
struct observable {
    using observed_t = T;
    
    //With an implicit default constructor we can assign `{}` instead
    //of the explicit version `observable<int>{}`, but I consider this
    //an error-prone assignment because the programmer can believe
    //that he/she is defining a default constructed
    //`observable<T>::observed_t` but in reality the left hand side
    //observable will be "erased", which means that all observers will
    //be removed.
    explicit observable() = default;
    
    explicit observable(observed_t o) : _observed(std::move(o)) {}
 
    observable(observable&& rhs) = default;
    observable& operator=(observable&& rhs) = default;
 
    template<typename U>
    std::enable_if_t<
        !std::is_same_v<std::remove_reference_t<U>, observable>,
        observable&>
    operator=(U&& rhs) {
        _observed = std::forward<U>(rhs);
        _after_change(_observed);
        return *this;
    }
 
    template<typename F>
    auto after_change(F&& f)
    { return _after_change.connect(std::forward<F>(f)); }
    
    const observed_t& observed() const noexcept
    { return _observed; }
private:
    observed_t _observed;
    boost::signals2::signal<void(T)> _after_change;
};

int main(){
    observable<int> o;
    o.after_change([](auto v){ std::cout << "changed to " << v << std::endl; }); //[1]
    o = 5;
 
    //We're not allowed to do the assignment `o = {}`. The programmer
    //should be explicit if he/she desires to "clean" the observable.
    o = observable<int>{};
    
    o = 10; //the above reaction [1] is not called;
    
    //outputs:
    //changed to 5
}