1

I read this from A tour of C++,

"Unlike an ordinary function, a constructor is guaranteed to be used to initialize objects of its class. Thus, defining a constructor eliminates the problem of uninitialized variables for a class."

How does this initialization work? E.g., suppose that I have a class with a field "s" of type string

class C{   
     std::string s;   
     ...

}

How does "s" get initialized, and would its value be guaranteed to be the empty string whatever the compiler?

zell
  • 9,830
  • 10
  • 62
  • 115
  • 4
    What book are you using? This should be covered in a [good C++ book](http://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list) – NathanOliver Dec 06 '19 at 13:59
  • _would its value be guaranteed to be the empty string whatever the compiler?_ No, it wouldn't. If very much depends on which constructor of `C` is called and how it is defined. – Daniel Langr Dec 06 '19 at 14:02
  • 1
    Note that there are plenty of on-line material about this topic, e.g. https://en.cppreference.com/w/cpp/language/initializer_list or https://en.cppreference.com/w/cpp/language/default_constructor – Bob__ Dec 06 '19 at 14:03
  • 1
    @Bob__ You should change your display name to __Bob, which would make your behavior undefined ;-) – Daniel Langr Dec 06 '19 at 14:05
  • 1
    @DanielsaysreinstateMonica Well, that's what I was trying to avoid, unsuccessfully I'm afraid ;) – Bob__ Dec 06 '19 at 14:08
  • In your code, you do not show a ctor for class C, so the compiler will provide a default ctor. AFAIK, the default ctor provided does nothing. However, note that std::string also has a default initialization. In the form of your declaration, see https://en.cppreference.com/w/cpp/string/basic_string/basic_string: "1) Default constructor. Constructs empty string (zero size and unspecified capacity). ..." – 2785528 Dec 06 '19 at 14:11
  • I'm guessing there is context around that sentence, discussing the difference from using a separate and explicit initialization function. In isolation, it's nonsense. – molbdnilo Dec 06 '19 at 14:12
  • 1
    @2785528 _the default ctor provided does nothing_ — It doesn't do nothing. The compiler-provided default constructor default-constructs all subobjects, including `s`. – Daniel Langr Dec 06 '19 at 14:12
  • @DanielsaysreinstateMonica - I can believe that ... but I've not seen it stated. I would be interested in how to confirm that the compiler-generated-C ctor invokes the default string ctor. I am thinking that the std::string ctor is invoked when ever it is declared on the automatic memory, even when _not_ in a class scope ctor. – 2785528 Dec 06 '19 at 14:21
  • @2785528 See http://eel.is/c++draft/class#base.init-9. – Daniel Langr Dec 06 '19 at 14:31

2 Answers2

4

The passage means that, if you have an Initialise() function, someone might forget to call it. It is not "guaranteed to be used", because you cannot control people. On the other hand, you cannot "forget" to call a constructor, because you never call a constructor: the computer does that for you when you instantiate your object.

Of course, that doesn't mean that the constructor is guaranteed to be properly written; that's still down to you. In the case of a class like std::string, though, it's pretty hard to get that wrong as it'll at least be default-constructed if you do not write code to do something else instead.

That happens whether or not you have an Initialise() function that's supposed to be called later, but if you did put some more complex initialisation in your constructor then you can be assured that this code will run.

// Bad! People can forget to call Initialise, so the string might stay empty
class Foo
{
public:
    void Initialise(const bool yesOrNo)
    {
        m_string = (yesOrNo ? "Yes!" : "No");
    }

private:
    std::string m_string;
};

// Good! You'll always have a value; no way around that
class Bar
{
public:
    Bar(const bool yesOrNo)
       : m_string(yesOrNo ? "Yes!" : "No")
    {}

private:
    std::string m_string;
};

Further reading:

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • `Foo *f = (Foo*)malloc(sizeof(Foo));` <-- constructor not called. (Technically undefined behaviour, but in practice, this allocates an object which is usable except for the fact the constructor is not called) – user253751 Dec 06 '19 at 14:13
  • 1
    @user253751 *but in practice, this allocates an object which is usable except for the fact the constructor is not called* No, it doesn't. The only way to create an object in C++ is with a definition, by a new-expression, when implicitly changing the active member of a union, or when a temporary object is created. [reference](https://timsong-cpp.github.io/cppwp/intro.object#1.sentence-2) – NathanOliver Dec 06 '19 at 14:16
  • 1
    @user253751 Well, you said it: undefined behaviour. I concede that my answer is predicated on the idea that you properly declare your object, and that there are still ways to cheat the system, but anyway I believe this is what the textbook is trying to get at. As for _"in practice, this allocates an object which is usable except for the fact the constructor is not called"_, that's a belief you really need to get yourself out of. :) – Lightness Races in Orbit Dec 06 '19 at 14:16
  • @LightnessRaceswithMonica Try it on your favourite compiler, if you don't believe me. – user253751 Dec 06 '19 at 14:37
  • 1
    @user253751 I believe you that it will compile, and may even appear to work. However, your definition of "usable" is, at best, optimistic. If you have this code in your project, you should fix it, or you _will_ have a hidden bug. Never underestimate the compiler's drive to assume your program has no UB. You never created an object, so you're at risk of all sorts of weirdness. Remember, you're not programming a computer; you're describing a program, abstractly, and you _must_ follow the rules laid out for doing so, unless you want pain. – Lightness Races in Orbit Dec 06 '19 at 14:48
  • @user253751 Note where I said "you never created an object". I chose my words carefully. That's not "the constructor wasn't called"; it's _"you never created an object"_. And I cannot emphasise that enough. – Lightness Races in Orbit Dec 06 '19 at 14:50
  • @LightnessRaceswithMonica Did you also see where I said "in practice", meaning, "this is what the actual computer with the actual compiler is likely to do when you actually run this actual program, regardless of what the theory actually says"? – user253751 Dec 06 '19 at 14:50
  • 2
    @user253751 Reasoning about undefined behavior by observing expected behavior is nonsense. Even _"this allocates an object"_ is incorrect. No object is allocated, just raw memory is allocated. – Daniel Langr Dec 06 '19 at 14:50
  • @user253751 Yes, I did, and it's wrong. [It's _not_ "theory", some pie-in-the-sky ideal that someone made up for absolutely no reason and that you can ignore.](https://stackoverflow.com/questions/54120862/does-the-c-standard-allow-for-an-uninitialized-bool-to-crash-a-program#comment95074180_54120862) You really want to get out of the habit of thinking of it that way. Of course I can't force you to do that. All I can do is try to help you. You have to actually be willing to learn something. :) – Lightness Races in Orbit Dec 06 '19 at 14:51
  • @LightnessRaceswithMonica You do not think that compilers and computers work that way? – user253751 Dec 06 '19 at 14:52
  • @LightnessRaceswithMonica You will also note that nowhere did I say it worked *well*. – user253751 Dec 06 '19 at 14:57
  • @user253751 Actually, even if some (or a lot of) compilers may have handle the case of an UB by making a choice to make it predictable, you will never have the guarantee that all compilers have handled it, neither that they have made the same choice, ... Your program based on so called _"practice"_ will be dependent of a specific compiler at a specific version with specific flags, for specific platform, ... So in other words, you don't avoid UB. If someone else build your code with another compiler, you can easily see that your assumption becomes wrong. – Fareanor Dec 06 '19 at 15:04
  • Since compilers have the choice to handle it or not (i.e. the standard says it is undefined behaviour), you can't infer anything else than that it **is** undefined behaviour. – Fareanor Dec 06 '19 at 15:04
  • @Fareanor I can infer that I've never heard of a compiler where it would not be the case. Usually optimizers don't know about `malloc` so they have no reason to presume it would not return a pointer to a `Foo`. – user253751 Dec 06 '19 at 15:07
  • 1
    @user253751 I hope you don't work in a safety critical environment. UB in any form is just bad practice, even if the compiler does the sane thing. Not sure about you but having guaranteed behavior is what I always strive for. – NathanOliver Dec 06 '19 at 15:16
  • @NathanOliver-ReinstateMonica I also never said it wasn't bad practice. I hope *you* never work in a safety-critical environment, because you appear to have no idea at all how your compiler is likely to handle UB. – user253751 Dec 06 '19 at 15:16
1

When your instance of C is made, the std::string s is initialized in what is known as Member initialization.

How does "s" get initialized, and would its value be guaranteed to be the empty string whatever the compiler?

You're not showing us how you create C, so we don't know what constructor is used. If the default constructor is used, then the default constructor of std::string is used for s. This does indeed make an empty string, as explained here:

Default constructor. Constructs empty string (zero size and unspecified capacity).

Blaze
  • 16,736
  • 2
  • 25
  • 44