Search code examples
c++classexternbehavior

Difference between apparently equivalent expression (class)A a;, and (class)A a{};


I wonder, what could be a possible inherent difference between A a; and A a{}; (class A, no constructor)?

I was just playing with code, and found out something very interesting, where declaring a global class instance as A a; was producing a different result compared to using A a{}; instead.

This is intriguing because I've understood that:

  • Declaring a class instance without initialization calls the default constructor (constructor with no parameters, if it exists).

  • a{} is an expression that creates a class instance and calls the default constructor.

So, basically the same thing, but they did produce different results in a certain condition, although it's found in the realm of possible UB by breaking ODR.

My question is, how can a program produce a different result (even though it is a wrong code), if A a; is strictly equivalent to A a{};?

Logically, there should be some difference between their internal implementation to produce a different result, while everything else stays exactly the same. I wonder what that difference is.

I'm using Visual studio 2017.

//extern_src.cpp

class A {
public:
    int a=24;
};

A extern_a; // << here, if i put {} at the end, different results comes out;

//src.cpp

#include <iostream>
class A {
public:
    int a=3;
};

int main() {
    extern A extern_a;
    A a;
    std::cout << a.a << " " << extern_a.a << std::endl;
    return 0;
}

Without {}, the code prints out 3 3. So for both a and extern_a, the constructor of class A, which is defined in src.cpp, is called.

With {}, the code prints out 3 24. In this case, the constructor that's defined in extern_src.cpp is called for extern_A.

It seems like the default constructor is not immediately called, but only called later in the compile process in the first case (class instance declaration without {}).

I do understand this is breaking ODR by having multiple definitions for a single class. But my question is, if A a; is completely equivalent to A a{};, as they are both calling the default constructor, then how can they ever produce a different result?


Solution

  • You have already shown that your program violates the One Definition Rule. Per [basic.def.odr]/12:

    There can be more than one definition of a class type [...] Given such an entity named D defined in more than one translation unit, then

    • each definition of D shall consist of the same sequence of tokens; and

    • [...]

    [...] If the definitions of D do not satisfy these requirements, then the behavior is undefined.

    Your definitions of A do not consist of the same sequence of tokens, so the behavior is undefined. When the behavior is undefined all bets are off. Even the same program can behave differently when compiled again. Not to say that A a; and A a{}; are different statements (or more precisely, simple-declarations, but not expressions). Per [defns.undefined]:

    undefined behavior

    behavior for which this document imposes no requirements

    [ Note: Undefined behavior may be expected when this document omits any explicit definition of behavior or when a program uses an erroneous construct or erroneous data. Permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message). Many erroneous program constructs do not engender undefined behavior; they are required to be diagnosed. Evaluation of a constant expression never exhibits behavior explicitly specified as undefined in [intro] through [cpp] of this document ([expr.const]). — end note ]

    See also Undefined, unspecified and implementation-defined behavior to get an idea of the possible consequences of undefined behavior. In particular, one version of GCC (1.17) tried to start the games NetHack, Rogue, and Towers of Hanoi when encountering certain kinds of undefined behavior. [1]