7.5.4. Implicit Class-Type Conversions

Image

As we saw in § 4.11 (p. 159), the language defines several automatic conversions among the built-in types. We also noted that classes can define implicit conversions as well. Every constructor that can be called with a single argument defines an implicit conversion to a class type. Such constructors are sometimes referred to as converting constructors. We’ll see in § 14.9 (p. 579) how to define conversions from a class type to another type.


Image Note

A constructor that can be called with a single argument defines an implicit conversion from the constructor’s parameter type to the class type.


The Sales_data constructors that take a string and that take an istream both define implicit conversions from those types to Sales_data. That is, we can use a string or an istream where an object of type Sales_data is expected:

string null_book = "9-999-99999-9";
// constructs a temporary Sales_data object
// with units_sold and revenue equal to 0 and bookNo equal to null_book
item.combine(null_book);

Here we call the Sales_data combine member function with a string argument. This call is perfectly legal; the compiler automatically creates a Sales_data object from the given string. That newly generated (temporary) Sales_data is passed to combine. Because combine’s parameter is a reference to const, we can pass a temporary to that parameter.

Only One Class-Type Conversion Is Allowed

In § 4.11.2 (p. 162) we noted that the compiler will automatically apply only one class-type conversion. For example, the following code is in error because it implicitly uses two conversions:

// error: requires two user-defined conversions:
//    (1) convert "9-999-99999-9" to string
//    (2) convert that (temporary) string to Sales_data
item.combine("9-999-99999-9");

If we wanted to make this call, we can do so by explicitly converting the character string to either a string or a Sales_data object:

// ok: explicit conversion to string, implicit conversion to Sales_data
item.combine(string("9-999-99999-9"));
// ok: implicit conversion to string, explicit conversion to Sales_data
item.combine(Sales_data("9-999-99999-9"));

Class-Type Conversions Are Not Always Useful

Whether the conversion of a string to Sales_data is desired depends on how we think our users will use the conversion. In this case, it might be okay. The string in null_book probably represents a nonexistent ISBN.

More problematic is the conversion from istream to Sales_data:

// uses the istream constructor to build an object to pass to combine
item.combine(cin);

This code implicitly converts cin to Sales_data. This conversion executes the Sales_data constructor that takes an istream. That constructor creates a (temporary) Sales_data object by reading the standard input. That object is then passed to combine.

This Sales_data object is a temporary (§ 2.4.1, p. 62). We have no access to it once combine finishes. Effectively, we have constructed an object that is discarded after we add its value into item.

Suppressing Implicit Conversions Defined by Constructors

We can prevent the use of a constructor in a context that requires an implicit conversion by declaring the constructor as explicit:

class Sales_data {
public:
    Sales_data() = default;
    Sales_data(const std::string &s, unsigned n, double p):
               bookNo(s), units_sold(n), revenue(p*n) { }
    explicit Sales_data(const std::string &s): bookNo(s) { }
    explicit Sales_data(std::istream&);
    // remaining members as before
};

Now, neither constructor can be used to implicitly create a Sales_data object. Neither of our previous uses will compile:

item.combine(null_book);  // error: string constructor is explicit
item.combine(cin);        // error: istream constructor is explicit

The explicit keyword is meaningful only on constructors that can be called with a single argument. Constructors that require more arguments are not used to perform an implicit conversion, so there is no need to designate such constructors as explicit. The explicit keyword is used only on the constructor declaration inside the class. It is not repeated on a definition made outside the class body:

// error: explicit allowed only on a constructor declaration in a class header
explicit Sales_data::Sales_data(istream& is)
{
    read(is, *this);
}

explicit Constructors Can Be Used Only for Direct Initialization

One context in which implicit conversions happen is when we use the copy form of initialization (with an =) (§ 3.2.1, p. 84). We cannot use an explicit constructor with this form of initialization; we must use direct initialization:

Sales_data item1 (null_book);  // ok: direct initialization
// error: cannot use the copy form of initialization with an explicit constructor
Sales_data item2 = null_book;


Image Note

When a constructor is declared explicit, it can be used only with the direct form of initialization (§ 3.2.1, p. 84). Moroever, the compiler will not use this constructor in an automatic conversion.


Explicitly Using Constructors for Conversions

Although the compiler will not use an explicit constructor for an implicit conversion, we can use such constructors explicitly to force a conversion:

// ok: the argument is an explicitly constructed Sales_data object
item.combine(Sales_data(null_book));
// ok: static_cast can use an explicit constructor
item.combine(static_cast<Sales_data>(cin));

In the first call, we use the Sales_data constructor directly. This call constructs a temporary Sales_data object using the Sales_data constructor that takes a string. In the second call, we use a static_cast4.11.3, p. 163) to perform an explicit, rather than an implicit, conversion. In this call, the static_cast uses the istream constructor to construct a temporary Sales_data object.

Library Classes with explicit Constructors

Some of the library classes that we’ve used have single-parameter constructors:

• The string constructor that takes a single parameter of type const char*3.2.1, p. 84) is not explicit.

• The vector constructor that takes a size (§ 3.3.1, p. 98) is explicit.


Exercises Section 7.5.4

Exercise 7.47: Explain whether the Sales_data constructor that takes a string should be explicit. What are the benefits of making the constructor explicit? What are the drawbacks?

Exercise 7.48: Assuming the Sales_data constructors are not explicit, what operations happen during the following definitions

string null_isbn("9-999-99999-9");
Sales_data item1(null_isbn);
Sales_data item2("9-999-99999-9");

What happens if the Sales_data constructors are explicit?

Exercise 7.49: For each of the three following declarations of combine, explain what happens if we call i.combine(s), where i is a Sales_data and s is a string:

(a) Sales_data &combine(Sales_data);

(b) Sales_data &combine(Sales_data&);

(c) Sales_data &combine(const Sales_data&) const;

Exercise 7.50: Determine whether any of your Person class constructors should be explicit.

Exercise 7.51: Why do you think vector defines its single-argument constructor as explicit, but string does not?


..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.205.99