V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
zxCoder
V2EX  ›  C++

关于 C++ default constructor 的疑问

  •  
  •   zxCoder · 2021-03-06 16:30:05 +08:00 · 2093 次点击
    这是一个创建于 1391 天前的主题,其中的信息可能已经有所发展或是发生改变。

    看 Essential C++这本书

    构造器这块实在很难理解,也可能是其他语言先入为主导致的...

    书上说默认构造函数有两种形式,一种是无参,一种是给每一个参数提供默认值。

    如果我这两种形式都定义了,这时候Triangular t1;就会出现 ambiguous 的编译错误,是因为 Triangular(int len = 1, int bp = 1)这个构造函数也被认为是无参构造?

    但是我如果再定义一个Triangular(int len, int bp);也不行,这时候Triangular(int len = 1, int bp = 1)这个构造函数是被认为是两个参数的构造函数?

    然后如果我的构造函数定义在 class 外,变成Triangular::Triangular(int len, int bp){}Triangular::Triangular(){},这也完全看不出这是两个冲突的 default constructor 了。。

    这几个关系搞得有点乱,有大佬说说 C++这块的设计哲学吗。。。总感觉有点难记

    class Triangular {
    public:
        Triangular();
    //    Triangular(int len);
        Triangular(int len = 1, int bp = 1);
    //    Triangular(int len, int bp);
    
    private:
        int _length;
        int _beg_pos;
        int _next;
    };
    
    Triangular::Triangular(int len, int bp) {
        _length = len > 0 ? len : 1;
        _beg_pos = bp > 0 ? bp : 1;
        _next = _beg_pos - 1;
    }
    
    Triangular::Triangular() {
        _length = 1;
        _beg_pos = 1;
        _next = 0;
    }
    
    
    int main() {
    //    Triangular t1;                   // 无参构造
        Triangular t2 = 5;                 // 一个参数的构造函数
        Triangular t3(1);            // 一个参数的构造函数
        Triangular t4(1, 2);  // 两个参数的构造函数
    //    Triangular t5();                 // 为了兼容 C,这被认为是函数调用而不是无参构造
        return 0;
    }
    
    14 条回复    2021-06-14 20:40:13 +08:00
    hello2060
        1
    hello2060  
       2021-03-06 17:02:25 +08:00 via iPhone   ❤️ 1
    已经忘了 c++了,随便猜猜

    第一种情况,一个无参数,一个默认参数,冲突是正常的啊。

    第二种情况,一个无参数,一个两个参数,第二个不是默认构造函数,也很正常啊
    hello2060
        2
    hello2060  
       2021-03-06 17:04:26 +08:00 via iPhone
    手机上看贴的,似乎你说的是第二种情况还是有默认参数?
    anonymous256
        3
    anonymous256  
       2021-03-06 17:36:19 +08:00
    “是因为 Triangular(int len = 1, int bp = 1)这个构造函数也被认为是无参构造?” 是啊
    你没提供参数,编译器不知道选择哪个构造函数了。 你需要移除一个构造函数
    hello2060
        4
    hello2060  
       2021-03-06 18:12:46 +08:00
    你的 code 里这种写法和第一种是一样的吧,默认参数只要在声明里指定,定义里不用吧
    ipwx
        5
    ipwx  
       2021-03-06 18:18:23 +08:00
    ... 为什么要用这种有歧义的语法。。。

    你难道会定义两个函数,一个 int f(); 一个 int f(int a=1, int b=2); 嘛?
    wutiantong
        6
    wutiantong  
       2021-03-06 19:23:28 +08:00
    1. 你定义函数时指定了一些参数
    2. 你为函数参数配置了一些默认值
    3. 你写一句调用函数的表达式时,编译器尝试通过各种上下文信息来找到正确的被调用函数(静态重载)

    以上是三件独立的事情,不要混淆在一起理解。
    yulon
        7
    yulon  
       2021-03-06 23:12:10 +08:00
    如果没有冲突的话,你觉得你要怎么调用,才能利用到 len = 1 这个默认值?
    GeruzoniAnsasu
        8
    GeruzoniAnsasu  
       2021-03-07 00:16:25 +08:00
    首先像 #6 说的先把默认构造 / 构造函数带有默认参数 / 编译器选择重载这三者区分开来

    Class() 无参默认构造
    Class(type param=1) 带有一个参数的构造函数,且这个函数有默认值

    定义的时候,这是两个不同的构造函数,这应该不难理解



    然后如果一个类同时存在这两个构造函数,当你写下 Class obj 时,编译器会寻找可以满足对象定义语义的构造函数,于是这两个函数都能被找到,于是会产生 ambiguous 。当你写 Class obj(1) 的时候,无参构造不能满足语义,只有第二个构造可以满足,因此无歧义
    dangyuluo
        9
    dangyuluo  
       2021-03-07 00:22:04 +08:00
    看编译器报错,找 candidate 关键词
    anonymous256
        10
    anonymous256  
       2021-03-10 18:54:19 +08:00
    我重新回答一下你的问题,。简化一下你的代码,下面这样的写法中,`Triangular t1; ` 语句同样会报错。

    ```cpp
    class Triangular {
    public:
    Triangular();
    Triangular(int len = 1, int bp = 1);
    private:
    int _length;
    int _beg_pos;
    int _next;
    };

    Triangular::Triangular(int len, int bp) {
    _length = len > 0 ? len : 1;
    _beg_pos = bp > 0 ? bp : 1;
    _next = _beg_pos - 1;
    }

    int main() {
    Triangular t1;
    return 0;
    }
    ```

    先说 C++的规则:

    1. 如果你没有提供<任何一个>构造器,编译器自动会为你提供 default constructor 。那么 “Triangular t1; ” 写法正确,并且它会调用默认的(编译器提供的)构造器给你用。

    2. 如果你提供了任意的一个非默认的构造器,比如 "Triangular(int a, int b);"。 那么 “Triangular t1; ” 写法错误。

    3. 如果你想要默认的构造器,但是你又想要自定义默认的构造器(由于你对编译器提供的那个默认构造器不满意)。你有两种方法:

    方法 A: 对已存在构造器参数提供默认值,也就你的这个:

    Triangular(int len = 1, int bp = 1);

    方法 B:对 C++默认的构造器重载,自定义它的实现:

    Triangular(); // 隐式声明:使用默认的糟糕器
    // Triangular()的代码实现 在别处

    切记:你有且只能拥有一个默认的构造器,你不可以想要拥有两个! 也就是说,方法 A 和方法 B,你只能选一个。

    而你的问题就出在这里:你实现了两个默认的构造器!如果存在两个可供选择的函数并且它们 [难分优劣] ,则编译器认为此次调用存在二义性并且报错。更具体的说,如果你同时实现了方法 A 和方法 B,当编译器执行到“Triangular t1; ” ,编译器即可以选择方法 A 定义的函数,也可以选择方法 B 定义的函数,编译器此时并不知道应该选择哪个最为恰当;编译器被搞晕了,它因此报错了。
    anonymous256
        11
    anonymous256  
       2021-03-10 19:12:59 +08:00
    顺便提一句,你这个 C++类构造器相关的语言特性和设计完全无关,而是函数重载的范畴。编译器无法选择相应的函数。

    int fct(){return 1;}

    int fct(int a=1, int b=2) {return a+b;}

    int main() {
    auto a = fct();
    return 0;
    }

    这样的写法同样报错:call of overloaded ‘fct()’ is ambiguous !
    levelworm
        12
    levelworm  
       2021-03-11 22:13:39 +08:00
    奇怪,照理说函数重载应该可以认出来这是俩函数啊,还是我记错了,擦。。。C++学的真不扎实。
    levelworm
        13
    levelworm  
       2021-03-11 22:19:41 +08:00
    欧我知道是什么原因了,其实原因是你第二个 constructor 里头有默认值,所以 Triangular t1 可以调用两个中的任意一个。你把第二个构造器的默认值改掉就成了。我想呢,这样应该没问题啊。

    https://onlinegdb.com/rkSxDjPmO
    shrikextl
        14
    shrikextl  
       2021-06-14 20:40:13 +08:00
    你想使用默认构造函数不就是为了调用方不给你提供任何参数就能构造一个对象,那么在完成这个目的上,无参默认构造函数和有默认参数的构造函数起到的作用是一样的,所以为什么要定义两个功能相同的构造函数
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5328 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 39ms · UTC 07:53 · PVG 15:53 · LAX 23:53 · JFK 02:53
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.