0

So i'm doing a beginner c++ course and i'm confused about something regarding parenthesis and curly brackets..

#include "Savings_Account.h"

Savings_Account::Savings_Account(double balance, double int_rate)
    : Account(balance), int_rate{int_rate} {
        
    }

Savings_Account::Savings_Account() 
    : Savings_Account{0.0, 0.0} {
        
    }

Note:Savings_account is a class derived from Account class

In the second line of code where the tutor writes :Account(balance), he says he is delegating to the account class's overloaded constructor which kinda makes sense but...there's more

class Derived : public Base {
private:
    int doubled_value;
public:
    Derived() : 
        Base {}  {
            cout << "Derived no-args constructor " << endl; 
    }
    Derived(int x) 
        : Base{x} , doubled_value {x * 2} { 
            cout << "int Derived constructor" << endl; 
    }
    Derived(const Derived &other)
        : Base(other), doubled_value {other.doubled_value} {
         cout << "Derived copy constructor" << endl;     
    }

In this code above which is from a different video that is part of the course, at line 10 where he writes :Base{x} he says he is delegating to the Base class's constructor which is where i'm confused cuz he used curly brackets and in line 14 where he writes :Base(other) he says he is delegating to the base class's copy constructor and he used parenthesis here so i'm getting quite confused.... Does using parenthesis or curly brackets matter? And How does the compiler know when he is referring to the copy constructor or normal args constructor?

And this is the base class in case u need it

private:
    int value;
public:
   Base()
        : value {0}  { 
            cout << "Base no-args constructor" << endl; 
    }
    Base(int x) 
        : value {x} { 
            cout << "int Base constructor" << endl;
    }
    Base(const Base &other) 
        : value {other.value} {
         cout << "Base copy constructor" << endl;     
    }
        
    Base &operator=(const Base &rhs)  {
    cout << "Base operator=" << endl;
        if (this == &rhs)
            return *this;
        value = rhs.value;
        return *this;
    }
            
   ~Base(){ cout << "Base destructor" << endl; }
};

This is the derived class

private:
    int doubled_value;
public:
    Derived() : 
        Base {}  {
            cout << "Derived no-args constructor " << endl; 
    }
    Derived(int x) 
        : Base{x} , doubled_value {x * 2} { 
            cout << "int Derived constructor" << endl; 
    }
    Derived(const Derived &other)
        : Base(other), doubled_value {other.doubled_value} {
         cout << "Derived copy constructor" << endl;     
    }
    
    Derived &operator=(const Derived &rhs) {
            cout << "Derived operator=" << endl;
        if (this == &rhs)
            return *this;
        Base::operator=(rhs);
        doubled_value = rhs.doubled_value;
        return *this;
    }
   ~Derived(){ cout << "Derived destructor " << endl; } 
};
  • 3
    Does this answer your question? [What are the differences between C-like, constructor, and uniform initialization?](https://stackoverflow.com/questions/24953658/what-are-the-differences-between-c-like-constructor-and-uniform-initialization) – Botje Nov 16 '20 at 16:16
  • 1
    In order to make C++ even more "interesting", it matters in some cases but not always. – molbdnilo Nov 16 '20 at 16:18
  • @molbdnilo but how will the compiler know that he is referring to the copy constructor or normal constructor? –  Nov 16 '20 at 16:21

2 Answers2

1

No.

The choice of brackets is not what determines whether you're delegating to another constructor or not.

The choice of brackets isn't entirely arbitrary, and it can make a difference to some things, but not for this.

It's unfortunate that the code's author has chosen to be confusing, by mixing initialisation styles, without even writing a comment to explain why they've done so.

Asteroids With Wings
  • 17,071
  • 2
  • 21
  • 35
  • But how does the compiler know when he is referring to copy constructor or normal constructor? –  Nov 16 '20 at 16:33
  • 2
    @Padawan The constructor chosen depends on the arguments, just like any function. – Asteroids With Wings Nov 16 '20 at 16:39
  • @Padawan _"But not for this"_ - the greedyness of overload resolution for choosing overloads (constructors as well as for free functions) whose argument has a matching `std::initializer_list` argument is notorious, and something you'd eventually want to know about. Had the author of your example wanted to implement overloaded constructors in `Base` with `std::initializer_list` argument(s), the choice between using direct list init vs direct initialization invocation makes a significant difference. – dfrib Nov 16 '20 at 16:54
0

In this particular example, and by speculation of the authors intent; no, using direct list initialization (Base{x}) as compared to direct initialization (Base(other)) should make no difference. We cannot know for sure, however, as we have not seen the implementation of Base.

When it comes to initialization, specifically overloading of constructors, there are significant differences between list initialization and non-list initialization: the former strongly favouring constructor overloads with a matching parameter of type std::initializer_list<T>.

E.g., consider this contrived and evil example:

#include <initializer_list>
#include <iostream>

class Base {
    int value{666};
public:
    Base(std::initializer_list<int>) { std::cout << __PRETTY_FUNCTION__; }
    Base(int val) : value(val) { std::cout << __PRETTY_FUNCTION__; }
};

class DerivedA : public Base {
    int doubled_value;
public:
    DerivedA(int x) : Base(x),  doubled_value(2 * x) {}
};

class DerivedB : public Base {
    int doubled_value;
public:
    DerivedB(int x) : Base{x},  doubled_value(2 * x) {}
};

int main() {
    DerivedA a{42}; // OK a.value is 42, a.doubled_value is 84
    DerivedB b{42}; // EH? b.value is 666, b.doubled_value is 84
}
dfrib
  • 70,367
  • 12
  • 127
  • 192
  • how is a.value 42 when u set value as 666 by default –  Nov 16 '20 at 17:54
  • @Padawan That is the whole essence of my answer: because in `DerivedB b{42};` the constructor of `DerivedB` contains the expression `Base{x}`, which uses _list initialization_, thus choosing the `Base(std::initializer_list)` of `Base, which _does not make use of the value passed to it_, this falling back on the default member initializer `{666}` to the `value` data member of `Base`. For the example `DerivedA a{42};`, the call to the `Base` constructor does not use list initialization (`Base(x)`) and thus the `Base(int val)` is chosen, initializing `value` with the passed argument. – dfrib Nov 16 '20 at 17:59
  • @Padawan Ah, you may mean why a default member initializer is not always chosen? The default member initializer is _only chosen for constructors where the same data member is not present in any member initializer list_, as is the case with the `Base(std::initializer_list)` constructor (no member initializer list). – dfrib Nov 16 '20 at 18:00
  • i understand most of what u just said, but i dont know what std::initializer_list means, i'm referring to the comment before this latest one u posted cuz as soon as i sent my comment, it showed the new one u sent –  Nov 16 '20 at 18:05
  • and why does the constructor of derivedb take Base(std::initializer_list), why does it having Base{x} make it choose Base(std::initializer_list) –  Nov 16 '20 at 18:10