简体   繁体   中英

C++ Primer 5th Edition (Stanley) - Question on exercise 7-39

Working through some exercises in C++ Primer 5th Ed (Stanley) and on exercise 7-39.

  1. Class for exercise 7-39.

     class Sales_data { public: // defines the default constructor as well as one that takes a string argument Sales_data(std::string s = ""): bookNo(s) {} // remaining constructors unchanged Sales_data(std::string s, unsigned cnt, double rev): bookNo(s), units_sold(cnt), revenue(rev*cnt) {} Sales_data(std::istream &is) { read(is, *this); } // remaining members as before
  2. Question:
    Exercise 7.39: Would it be legal for both the constructor that takes a string and the one that takes an istream& to have default arguments? If not, why not?

  3. My answer:
    Yes. I can have the following:

     Sales_data(std::string s = ""): bookNo(s) {} Sales_data(std::istream &is = std::cin) { read(is, *this); }

The above are valid.
However there are some site with solution for this C++ Primer 5th edition which says no as below:

https://github.com/Mooophy/Cpp-Primer/tree/master/ch07 (you need to scroll down to exercise 7-39. Answer does not make sense)

https://github.com/jaege/Cpp-Primer-5th-Exercises/blob/master/ch7/7.39.md (this answer does not make sense at all)

I would like some other opinion if i am missing something. I still stand by my answer to far.

Thx

Code can have more than one default constructor, and still be well formed.

But there's a bit of a trick to that: if you have more than one default constructor, you can't ever default-construct objects of that type, because the attempt at default construction would be ambiguous (ie, the compiler wouldn't know which default constructor to use).

For example, consider something like this:

class foo {
    int x;
public:
    foo() : x(1) {}
    foo(int x = 0) {}
};

By itself, that much is well-formed code (ie, a properly functioning compiler should accept it--though it may well give a warning about it).

But if you try to default construct an object of that type:

int main() { 
    foo f;
}

... that code is ill formed (ie, a properly functioning compiler should reject it).

That said, most reasonably sane people looking at your code would generally consider this a bad idea. When somebody sees a constructor like foo(int x=0) , they tend to presume that the intent is that you can default-construct an object of that class. But if you have ambiguous constructors, you can't, so the code is basically lying.

Summary

At least from the compiler's viewpoint, creating code that could lead to ambiguity isn't necessarily a problem in itself. It's only when you try to use it in a way that triggers the ambiguity that your code is ill formed (and even then, it's the code that tries to use the ambiguous constructors that's ill-formed, not the code that creates the potential ambiguity).

Also note this isn't specific to constructors. Other overloaded functions are the same way. Two overloaded functions that provide default arguments for all parameters work out the same way--having the of them exist is allowed, but any code that attempts to invoke that overloaded function without an argument so it triggers the ambiguity is ill-formed.

If you make two default constructors, you effectively have no usable default constructor. If you try to default construct a Sales_data when you have more than one default constructor, there will be ambiguity and the program will fail to compile.

So, this is pointless

Sales_data(std::string s = "") : bookNo(s) {}
Sales_data(std::istream &is = std::cin) { read(is, *this); }

and better written like this:

Sales_data(std::string s) : bookNo(s) {}
Sales_data(std::istream &is) { read(is, *this); }

No, you cannot have both. It would result in a compilation error C2668: 'Sales_data::Sales_data': ambiguous call to overloaded function when you try to construct the object without any argument, as the example shown below.

#include <string>
#include <iostream>

class Sales_data { 
    public: 
    Sales_data(std::string s = "") {
        std::cout << "1st constructor"  << std::endl;
    } 
    Sales_data(std::string s, unsigned cnt, double rev) {
        std::cout << "2nd constructor"  << std::endl;
    } 
    Sales_data(std::istream &is = std::cin) {
        std::cout << "3rd constructor"  << std::endl;
    }
};

int main() {
    Sales_data s;  // The compiler would fail here. :)
    return 0;
}

Notice that the definition of Sales_data s; in the main function is ambiguous because the complier does not know if it should call Sales_data(std::string s = "") or Sales_data(std::istream &is = std::cin) to construct the object.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM