C++ (Part2)

Posted on at


Accidentally, one day I requested multiplication by a double:

1

2

FixedDecimal<2> d ("1.20");

assert ((d * 0.50).to_string() == "0.60");

The program compiles, and only later do I find that the result is not 0.60 as I expected, but is 0.00 instead.

Do you know why? It has been already described in Inadvertent conversions. In short,FixedDecimal never intended to inter-operate with doubles; but it inter-operates with ints, and because doubles are implicitly convertible to ints…

Again, sometimes a conversion form double to int may be exactly what you need. But sometimes, especially in types modeling mathematical numeric concepts, this implicit conversion may be disastrous.

An important special case of implicit conversions kicking in when you least expect them, is when you inadvertently provide your own. Unless you mark your constructors explicit, especially those that can be called with one argument, you define an implicit conversion: sometimes one that you never wanted. Imagine the following piece of code:

1

2

string xml = "<root><elem>1</elem></root>";

parse_xml(xml, dom);

This program looks as though it might be doing something reasonable. It compiles; it is supposed to populate object dom with the xml content, and you might be thinking that this is the case, but in fact it does something else. This is because function parse_xml is declared as:

1

bool parse_xml(File const& file, Dom& dom);

And it so happens that File has the following constructor:

1

2

3

4

5

6

class File

{

public:

  File(std::string const& name, std::string mode = "r");

  // ...

};

It is not even a single-argument constructor, but because it has default values on the other arguments, it still can be used for conversions. Perhaps, the constructor didn’t even have the default arguments in the past, but they were added later. Had the compiler not been so generous in using our constructors for conversions, we would have spotted the problem during the compilation.

The existence of these “invisible functions” leads to conclusion that visible function declarations are not the best way to describe the type’s interface. Instead it is better to describe the interface by listing what expressions are allowed. This way we take into account not only the explicitly declared functions, but also all special functions and function calls injected by the compiler. This is, for instance, what Concepts Lite do. Let’s look at a sample concept:

1

2

3

4

5

6

7

8

9

// NOT C++ YET

template <class T>

concept bool Scalable = requires(T v, double s)

{

  v *= s;

  v /= s;

  { v * s } -> T;

  { v / s } -> T;

};

The concept requires that multiplication and division by a double works, but does not care whether T declares them as member (or non-member) functions, or if they are achieved through implicit conversions from double to int.

What can we do?

So, that’s what we have to live with. Compilers need to accept what the C++ Standard tells them to accept; and sometimes what the C++ Standard requires, in connection with our oversight may cause a bug.

But there are precautions we can take ourselves to reduce the chances of hitting one of these weaknesses of the language. Regarding the implicitly declared (and defined) member functions, one way of playing it safer is to apply the Rule of Zero, which can be read as follows. One class either only manages a resource (and does nothing else), or offers some program logic (and manages no resource directly). If some class does both, split it into two. In the class that manages the resource — only in this case is there a chance to leak a resource — you have to be careful to declare all the special member functions yourself. One way to do it safely is to first declare all the special member functions as deleted, and only re-enable them as needed. This way we start with the safe default. Or to derive (privately) all resource-handling classes from something similar to boost::noncopyable, that has all the special members inaccessible, and therewith prevents the default generation of these members in our class.

Also, note that since C++11, this behavior, where you have a custom destructor and compiler defines a shallow copy constructor, while still normative, is deprecated. Compilers must still do it, but what they can also do is to issue a warning that they are doing a deprecated and potentially unsafe thing. This is year 2015, we have been aware of this problem for at least 17 years, so you might expect that every modern compiler does that.

I have checked four compilers: GCC, Visual C++, Clang and ICC, to see if they give me a warning when I try to compile the example with the String above.

Clang with flag -Wdeprecated gives me a warning: definition of implicit copy constructor for ‘String’ is deprecated because it has a user-declared destructor ICC with flag -Weffc++ gives: warning #2021: Effective C++ Item 11 declare a copy constructor and assignment operator for String.

GCC with flag -Weffc++ gives: ‘class String’ has pointer data members but does not override ‘String(const String&)’ or ‘operator=(const String&)’.

I couldn’t make Visual C++ to give me a warning about the problem. It warns me about other things (it considers calling strcpy unsafe), but not about the real issue here.

Personally, I feel only Clang does it the right way. It is good that I get any warning In ICC and GCC, but the reasons for so doing (that I have a pointer member) seem wrong.

Regarding the inadvertent conversion from double, both GCC and Clang offer a warning that can detect it: -Wfloat-conversion. Also warning C4244 in Visual C++ can detect it, but the latter warning detects much more, perhaps too much.

If you want to disable the inadvertent conversion selectively in numeric types, what you can do is to declare additional overloads with deleted definitions:

1

2

3

4

5

6

7

8

9

10

template <int Dec>

class FixedDecimal

{

  // ...

  FixedDecimal& operator *= (int s) { /*implement*/ }

  FixedDecimal& operator *= (double s) = delete;

 

  FixedDecimal& operator /= (int s) { /*implement*/ }

  FixedDecimal& operator /= (double s) = delete;

};

This way we have a dedicated overload for type double, except that it will fail the compilation when selected. The only problem with it is that now, practically for every function taking type int you have to provide the ‘shadow’ function taking double. The interface doubles (pun intended), but the bigger problem is that you will likely forget to provide the shadow deleted function at some point.

As an alternative, instead of int you can use a replacement, say only_int which is implicitly convertible from int, but not double, and probably not unsigned int:

1

2

3

4

5

6

7

template <int Dec>

class FixedDecimal

{

  // ...

  FixedDecimal& operator *= (only_int s) { /*implement*/ }

  FixedDecimal& operator /= (only_int s) { /*implement*/ }

};



About the author

toshi-warraich

Good Boy with very bad thoughts.

Subscribe 0
160