0

If a class is written well, you should be able to gleam all relevant information about the class simply by looking at its header.

If one sees that the constructor is marked explicit:

  1. What should one assume to be absolutely true about this class?
  2. What should one assume to be potentially true about this class.
  3. What sort of bugs could one expect, if explicit is violated somehow?

If what I am asking is not clear; In the same way that if I see a member function marked virtual void sneed() = 0;

  1. Its absolutely true that this is an abstract class
  2. Its potentially true that this is some sort of factory
  3. And that if I do not impliment it, the program will not compile.

Thanks.

Anon
  • 3,565
  • 3
  • 27
  • 45
  • 1
    IMO, this is not a particularly useful way to think about these things. These keywords tell you some things, but a big part of "all relevant information" comes from the meaning of the class and the methods - i.e., it might come from documentation comments present in the header. E.g. for your `virtual` example, it is in general not at all likely that it is some sort of a factory. This might be true in the context of particular codebase, but in general, it doesn't have to be a factory *in any way*. For `explicit`, you can infer that the devs wanted to prevent implicit conversions in client code. – Filip Milovanović Jul 08 '22 at 22:39
  • Based on `virtual void sneed() = 0;`, I would *not* conclude that this is particularly likely to be a factory. Pure virtual functions are used a great deal outside of factories. I suppose there may be some naming convention in which `sneed` would indicate something related to a factory (but if so, I don't recall having seen it). – Jerry Coffin Jul 09 '22 at 00:08
  • @FilipMilovanović I changed "likely" to "potentially". As for `explicit` -- I am not asking so much for the "what does explicit do" but rather for the "why" -- Why do classes ever need `explicit`? I could be wrong about this, but I think that QObject needs explicit, because QObject looks to disable the copy constructor. – Anon Jul 09 '22 at 00:41
  • 1
    So, the idea is that, when you put `explicit`, you prevent the compiler from making implicit conversions using that constructor. E.g., when a function takes a float, and you type in an int like `42`, it is implicitly converted to a float, but if a function takes an int, and you attempt to pass a float like `3.14`, you get a warning in C++ and an error in C#, because (a) a loss of data occurs, and you may care about that, and (b) even if you intended to do this, maybe the default rounding/truncation scheme doesn't do what you thought it does. So there's potential for subtle errors. 1/3 – Filip Milovanović Jul 09 '22 at 01:18
  • 1
    If someone placed `explicit` on their constructor, it indicates that they thought that implicit conversions (from the *type of the constructor argument* to the *type of the class*) would potentially cause more trouble then the convenience of implicit conversion was worth. Exactly *why* they thought this depends on what the actual class is supposed to represent and how it's meant to be used, and to some extent on their personal preferences. 2/3 – Filip Milovanović Jul 09 '22 at 01:18
  • 1
    As for `QObject`, the constructor takes in a `QObject*`, a pointer to a parent - which means that the compiler can use it to implicitly "convert" a pointer into a QObject. So, if you accidentally passed some pointer you had to a function that takes a QObject by value, the compiler would implicitly call that constructor to create a new QObject that has the pointer you passed in as a parent - which is probably not what you wanted; `explicit` prevents that. (BTW, you didn't mention QObject anywhere before your last comment, so I was talking in general terms) 3/3 – Filip Milovanović Jul 09 '22 at 01:19
  • @FilipMilovanović sorry to ask you to do this, but could you expand the last comment about QObject? I am having trouble parsing it. Are you saying that absent an `explicit` qualifier, the compiler will convert any pointer to a QObject...? – Anon Jul 09 '22 at 01:28
  • 1
    Not pointer of any type; if you already have a QObject-pointer (`QObject*`) to some qt object already in memory, and it needs to be assigned (maybe by accident) to a variable or a parameter of type `QObject`, the compiler has to figure out if that's valid. So it will go "Oh, look, there's a constructor that takes a single `QObject*` and spits out a `QObject`! Seems like it lets me convert between these two!". The problem is, the compiler is wrong about what this constructor does - it's not meant for conversions. Here's a [demo](https://onlinegdb.com/QhLWCVISK), click the "Fork" btn to edit. – Filip Milovanović Jul 09 '22 at 02:59
  • @FilipMilovanović All of that great information and examples should definitely have been in the Answer field, not the Comments :) – IMSoP Jul 09 '22 at 08:52

1 Answers1

2

Some constructor are conversion constructors, i.e. it can be used to transform an object of a type provided as argument, into an object of that class. This can sometimes lead to nasty bugs, when the compiler tries to use such a conversion behind the scene in an unexpected way (example).

If the conversion constructor is marked explicit, the compiler will not use it for conversions without an explicit request, i.e.

only where the direct-initialization syntax or where casts are explicitly used

Note that the explicit keyword may also be used with conversion functions, to prevent the same kind of problems.

In conclusion, you cannot infer anything general about the class. explicit has no impact on the class but only restricts the way its constructor (or conversion functions) may be used.

Christophe
  • 74,672
  • 10
  • 115
  • 187
  • Is any constructor not marked `explicit` technically a conversion constructor? – Anon Jul 09 '22 at 08:35
  • And if that is true, which I think it is ( https://en.cppreference.com/w/cpp/language/converting_constructor ) -- Going off the idea that QObject disables their converting constructors due to the fact that QObjects maintain a relationship with other objects, be it through a parent child heirarchy or a signal slot connection, that is not appropriate to simply copy ( Suppose you want to make a telephone pole; you wouldnt copy one that is already connected to a house, because you would then have two telephone poles and one house ) via a copy constructor, cont- – Anon Jul 09 '22 at 09:01
  • 2
    Indeed, according to the standard: "*A constructor that is not explicit specifies a conversion from the types of its parameters (if any) to the type of its class. Such a constructor is called a converting constructor.*" - This definition is very broad, and somewhat counter-intuitive. In practice, conversion constructors, i.e. with the true intent to convert (and perhaps a corresponding conversion function to convert back), are mostly with one argument of a different type. And here are most of the problems that explicit addresses. – Christophe Jul 09 '22 at 09:05
  • Could you not infer that in the case where all constructors are marked `explicit`, that this is due to the fact that this class ( Probably | Potentially ) maintains relationships with other objects? – Anon Jul 09 '22 at 09:07
  • @Anon Not really. Look at my example: the Test object is not related to the string object nor the contrary. The only "relation" is that the constructor has an argument of type string, whatever it means. It its the same kind of relationship that you have with any other argument in any other function, and `explicit` does't change the relationship (technically, in UML terms, it's called a dependency). It only reveals that there is a risk of ambiguity/confusion somewhere, that can be remediated with the explicit. – Christophe Jul 09 '22 at 09:16
  • I am not sure were on the same page. Your example does not use explicit, and the hypothetical purpose of your Test class in no way has any sort of perpetual dependancy on any other object, like a QObject would ( or a telephone pole ). – Anon Jul 09 '22 at 09:34
  • @Anon my demo shows that one may expect a string function to be called, but due to constness of an argument, the compiler introduces a conversion. There is a comment in the code suggesting to make the conversion constructor explicit. If you try, you'll see that the unexpected conversion des not happen, and that the compile will complain about the constness of the string argument (the root cause of the wrong conversion to happen). The point here is that `explicit` resolves an ambiguity regarding `f()` and its expected parameters and is only coincidentally related to the `Test` class. – Christophe Jul 09 '22 at 09:51
  • 2
    @Anon The conclusion of this example is that explicit can (and often does) solve ambiguities that are not intrinsically related to the class. And therefore my claim that you cannot deduce anything about the class itself. Your approach of drawing general hypotheses based on class definition is interesting. But to obtain valid results, the hypotheses need to be formulated in general and verified on specific cases/examples. The danger is to get biased by the example, and want to find a general rule that in reality applies only to some specific cases. – Christophe Jul 09 '22 at 09:53
  • 1
    I went through your class, ran the error, and I get everything you have said. Point made beautifully. But Im asking something more... annoyingly esoteric. Although I have thought a lot on it. I think there really is almost nothing general you could assume about a constructor marked explicit. If you think of something, let me know. Otherwise I'll leave it at this. – Anon Jul 09 '22 at 12:10
  • 2
    @Anon - there really isn't anything very general - except that the class didn't want to allow automatic conversions via that particular constructor. In some sense, it's really due to the fact that the C++ standard has this somewhat strange rule about implicit conversions, that makes the compiler do things that aren't always aligned with what the creator of the class had in mind. In C#, for example, it's the opposite: by default, no automatic conversion to the class is allowed, and to enable this, you have to use the `implicit operator` keyword. It's a quirk of C++. – Filip Milovanović Jul 09 '22 at 14:45