C + + primer (Fifth Edition) | exercise answer and analysis (Chapter 18: tools for large programs)

C + + primer (Fifth Edition) | exercise answer and analysis (Chapter 18: tools for large programs)

This blog mainly records the answers and analysis of exercises in C + + primer (Fifth Edition).
Reference resources: C++ Primer
C++Primer
C++Primer

Exercise 18.1

What is the type of exception object in the following throw statement?
(a)range_error r("error"); throw r;
(b) exception *p = &r; throw *p;
What happens if you write the thorw statement in (b) as throw p?

(a) The type of exception object in is range error, which is used to report range errors in internal calculations.
(b) The type of exception object in is exception. The static compile time type of the throw expression determines the type of the exception object.
If "thorw" in (b) is written as "throw p," a runtime error occurs.

Exercise 18.2

What happens when an exception occurs at the specified location?
void exercise(int *b, int *e)
{
vector<int> v(b, e);
int *p = new int[v.size()];
ifstream in("ints");
//Exception here
}

In case of an exception, the temporary variables before the block will be destroyed. v will call the destructor of vector class (standard library, P686) to destroy and release the relevant memory. P pointer will be destroyed, but the memory pointed to by P pointer is dynamically allocated, so the memory will not be released, causing memory leakage. The input stream object will call the destructor of ifstream class to destroy, and finally the program will be terminated. Standard library types ensure that their destructors do not throw exceptions.

Exercise 18.3

There are two solutions for the above code to work properly in the event of an exception. Describe the two methods and implement them.

Because only pointer P in the above code has memory leakage (exception), the problem to be solved is to automatically release the memory pointed to when an exception occurs.

  • Method 1: use the smart pointer and pass in the deleted lambda expression: shared_ptr < int > P (New Int [v.size()], [] (int * P) {delete [] p;}); / / lambda expression is equivalent to a remover.
  • Method 2: create a class containing an int * and delete the file pointer in the destructor.
class intAr  
{  
    int *p=nullptr;  
public:  
    intAr(size_t n):p(new int[n]){}  
    ~intAr()  
    {  
        delete[]p;  
    }  
}  

Exercise 18.4

Look at the inheritance system shown in Figure 18.1 (P693), explain what's wrong with the try block below and modify it.
try {
//Using C + + standard library
}
catch (exception) {
//...
}
catch (const runtime_error &re) {
//...
}
catch(overflow_error eobj){/**/}

P687
In the process of searching for matching catch statements, it is not necessarily the best match. It is the first catch statement that can be matched. Therefore, the more specialized and special catch statements are, the more they should be put in the front, because catch statements are matched one by one according to their occurrence order.
If there is such an inheritance relationship between the types of the catch statements, the lowest class of the inheritance chain should be placed first.
So we should put the lowest class in the first place and reverse the order.

Exercise 18.5

Modify the main function below. Enable it to catch any of the exception types shown in Figure 18.1 (P693):
int main(){
//Using C + + standard library
}
The processing code should first print the abnormal related error information, then call abort (defined in the cstdlib header file) to terminate the main function.

P173, each standard library exception class contains a member function named what, which has no parameters and the return value is a C-style string (const char *).

#include <iostream>  
#include <cstdlib>  
int main()  
{  
    using namespace std;  
    try {  
        //  
    }  
    catch (overflow_error e)  
    {  
        cout << e.what();  
        abort();  
    }  
    catch (underflow_error u)  
    {  
        cout << u.what();  
        abort();  
    }  
    catch (range_error r)  
    {  
        cout << r.what();  
        abort();  
    }  
    catch (domain_error d)  
    {  
        cout << d.what();  
        abort();  
    }  
    catch (invalid_argument i)  
    {  
        cout << i.what();  
        abort();  
    }  
    catch (out_of_range o)  
    {  
        cout << o.what();  
        abort();  
    }  
    catch (length_error l)  
    {  
        cout << l.what();  
        abort();  
    }  
    catch (runtime_error r)  
    {  
        cout << r.what();  
        abort();  
    }  
    catch (logic_error l)  
    {  
        cout << l.what();  
        abort();  
    }  
    catch (bad_alloc b)  
    {  
        cout << b.what();  
        abort();  
    }  
    catch (bad_alloc b)  
    {  
        cout << b.what();  
        abort();  
    }  
    catch (exception e)  
    {  
        cout << e.what();  
        abort();  
    }  
    return 0;  
} 

Exercise 18.6

The following exception types and catch statements are known. Write a throw expression so that the exception object it creates can be caught by these catch statements:
(a) class exceptionTYpe { }; catch(exceptionType *pet) { }
(b) catch(...) { }
© typedef int EXCPTYPE; catch(EXCPTYPE) { }

(a )throw &exceptionType()
(b) any abnormality, P688.
(c ) throw int()

Exercise 18.7

Define your own Blob and BlobPtr as described in Chapter 16. Note that the constructor is written as a function try statement block.

P690, just add try to the constructor.

template<typename T>  
Blob<T>::Blob() try:data(std::make_shared<std::vector<T>>()) {  
}  
catch (const std::bad_alloc &e) {  
    std::cerr << e.what() << std::endl;  
}  

Exercise 18.9

Define the bookstore program exception class described in this section, then rewrite a compound assignment operator for the sales data class and make it throw an exception.

The main need to add isbn_mismatch, an inherited exception class, can read.

Exercise 18.11

Why shouldn't the what function throw an exception?

The what function is used to represent the specific information of an exception.
Because it is noexcept, it cannot throw an exception. When the method it calls internally throws an exception, it will directly call std::terminate() to end the current program without being caught.

Exercise 18.13

When should unnamed namespaces be used?

P700, it is hoped that the defined object, function, class type or other entity can only be seen in a small piece of code of the program, which can further alleviate the namespace conflict.
P701, according to the C++11 standard, static definition of static variables has been canceled, and now it is to define a global unnamed namespace. Variables defined in unnamed namespaces are static.

Exercise 18.14

Suppose the following operator declares a member of the nested namespace mathLib::MatrixLib:
namespace mathLib {
namespace MatrixLib {
class matrix{/ /};
matrix operator
(const matrix &, const matrix &);
}
}

Note that each variable requires a scope qualifier.

mathLib::MatrixLib::matrix mathLib::MatrixLib::operator*(matrix &a,matrix &b);

Exercise 18.15

Describes the difference between the using directive and the using declaration.

P702, the using declaration statement introduces only one member of the namespace in turn. Using indicates that you cannot control which name is visible because all names are visible.

Exercise 18.16

Assuming that the place marked "location 1" in the following code is a declaration of using for all members in the namespace Exercise, explain the meaning of the code. What if these using statements appear in position 2? Change using to using instruction and answer the previous questions again.

Exercise 18.17

Actually write code to check whether your answer to the previous question is correct.

Test 1.1

namespace Exerciese {
	int ivar = 0;
	double dvar = 0;
	const int limit = 1000;
}
int ivar = 0;
//Location 1
using Exerciese::ivar;//Error, conflicts with global variable ivar, declared more than once  
using Exerciese::dvar;
using Exerciese::limit;
void manip()  
{	
	//Location 2
	double dvar = 3.1416;//Covering the dvar declared by using  
	int iobj = limit + 1;
	++ivar;  
	++::ivar;  
}  

Test 1.2

namespace Exerciese {
	int ivar = 0;
	double dvar = 0;
	const int limit = 1000;
}
int ivar = 0;
//Location 1

void manip()  
{	
	//Location 2
	using Exerciese::ivar;//Hide global variables   
	using Exerciese::dvar;
	using Exerciese::limit;
	double dvar = 3.1416;//Error, multiple definitions, multiple initializations, the current dvar object is already visible   
	int iobj = limit + 1;
	++ivar;  //ivar of Exerciese
	++::ivar; //global variable 
}  

Test 2.1

namespace Exerciese {
	int ivar = 0;
	double dvar = 0;
	const int limit = 1000;
}
int ivar = 0;
//Location 1
using namespace Exerciese;
void manip()  
{	
	//Location 2
	double dvar = 3.1416;//Covering the dvar declared by using  
	int iobj = limit + 1;
	++ivar;//Error, ambiguity, ambiguity, both visible  
	++::ivar;  
}  

Test 2.2

namespace Exerciese {
	int ivar = 0;
	double dvar = 0;
	const int limit = 1000;
}
int ivar = 0;
//Location 1
void manip()  
{	
	//Location 2
	using namespace Exerciese;
	double dvar = 3.1416;//Covering the dvar declared by using  
	int iobj = limit + 1;
	++ivar;//Error, ambiguity, ambiguity, both visible   
	++::ivar;  
}  

Exercise 18.18

It is known that there is a typical definition of swap (see section 13.3, P457). When meml is a string, which version of swap does the program use? What if meml is int? Describes the process of name search in both cases.
void swap(T v1, T v2) {
using std::swap;
swap(v1.mem, v2.mem);
//Exchange other members of type T
}

When the parameter is string, the swap function will be found in the string class first. If it is found, the std version will not be used. If it is of type int, the standard library version of swap is used directly.

Exercise 18.19

What happens if the call to swap is like std::swap(v1.mem1, v2.mem1)?

Only standard library versions of swap can be used.

Exercise 18.20

In the following code, determine which function matches the compute Call match. List all candidate functions and feasible functions, and what type conversion occurs for the matching process of parameters and parameters of each feasible function?
namespace primerLib {
void compute();
void compute(const void );
}
using primerLib::compute;
void compute(int);
void compute(double, double = 3.4);
void compute(char, char* = 0);
void f() {
compute(0);
}
//What happens if the using declaration is placed before the call point of the f function compute?

namespace primerLib  
{  
    void compute();//Infeasible  
    void compute(const void *);//Feasible, 0 - > null  
}  
using primerLib::compute;  
void compute(int);//Feasible, best match  
void compute(double, double = 1.1);//Feasible, int - > double  
void compute(char*, char* = 0);//Feasible, 0 - > null  
void f()  
{  
    compute(0);//Best match with compute(int) version  
}  
namespace primerLib{  
    void compute();//Infeasible, visible  
    void compute(const void *);//Feasible, 0 - > null, visible  
}  
void compute(int);//Feasible, invisible, hidden
void compute(double, double = 1.1);//Feasible, int - > double, hidden 
void compute(char*, char* = 0);//Feasible, 0 - > null, hidden
void f(){  
    using primerLib::compute;  
    compute(0);
}  

Exercise 18.21

Explain the meaning of the following statements. Are there any errors in them? If so, please point out and explain the cause of the error.

(a)class CADVehicle : public CAD, Vehicle{};
Cadvhicle inherits CAD publicly and Vehicle privately. Cadvhicle can get all the public and private methods of Vehicle, but it cannot be converted to the parameters of Vehicle. This is the "inaccessible" foundation. For example:

CadVehicle example;
void foo(Vehicle){/*do something*/};
foo(CADVehicle);//will not work, will work if Vehicle were public

(b)class DBList: public List,public List {/*do something*/};
Error because two attempts were made to derive from the same base class. If two different libraries or header files define the same named class, you need to use range resolution operators to specify, such as headerfile_name::List.
© class iostream: public istream, public ostream {/ * do something * /}; correct.

Exercise 18.22

It is known that there is an inheritance system for the following classes, where each class defines a default constructor:
class A {};
class B : public A{};
class C : public B{};
class X {};
class Y {};
class Z : public X, public Y {};
class MI : public C, public Z {};
class D : public X, public C{};
//What is the order in which constructors are executed for the following definitions?
MI mi;

The order in which base classes are constructed depends on the order in which they appear in the class derivation list. The structure is as follows: A, B, C, X, Y, Z, MI.

Exercise 18.23

Use the inheritance of exercise 18.22 and class D defined below, and assume that each class has a default constructor defined. Which class transformations are not allowed?
class D: public X, public c { ... };
D *pd = new D;
(a ) X *px = pd; (b ) A *pa = pd;
(c ) B *pb = pd; (d )C *pc = pd;

You can make a pointer or reference of an accessible base class point directly to a derived class object, but the pointer can only access the corresponding base class part or the base class part of the base class. So all transformations are allowed.

Exercise 18.24

P714, a series of calls are made by using a Bear pointer to execute the Panda object. Suppose we use a ZooAnimal pointer to the Panda object, please explain these call statements one by one.

ZooAnimal *pb = new Panda ("ying_yang");
pb->print();//Correct, belongs to ZooAnimal interface
pb->cuddle();//Error, not part of interface
pb->highlight();//Error, not part of interface
delete pb;//Correct, part of the interface

Exercise 18.25

Suppose we have two base classes Base1 and Base2,They each define a name called print And a virtual destructor. From these two base classes, we derive the following classes, which are all redefined print Function:
class D1 : public Base1{ //};
class D2 : public Base2{ /
/};
class MI : public D1, public D2 {/**/ };
//Indicate which function is used in each call by the following pointer:
Base1 *pb1 = new MI;
Base2 *pb2 = new MI;
D1 *pb1 = new MI;
D2 *pd2 = new MI;
(a ) pb1->print(); (b ) pd1->print(); (c ) pd2->print();
(d ) delete pb2; (e ) delete pd1; (f )delere pd2;

#include <iostream>
struct Base1
{
	virtual void print() { std::cout << "Print from Base1" << std::endl; }
	virtual ~Base1() { std::cout << "Base1" << std::endl; }
};
struct Base2
{
	virtual void print() { std::cout << "Print from Base2" << std::endl; }
	virtual ~Base2() { std::cout << "Base2" << std::endl; }
};

struct D1 : public Base1
{
	void print() override { std::cout << "Print from D1" << std::endl; }
	~D1() override { std::cout << "D1" << std::endl; }
};
struct D2 : public Base2
{
	void print() override { std::cout << "Print from D2" << std::endl; }
	~D2() override { std::cout << "D2" << std::endl; }
};
struct MI : public D1, public D2
{
	void print() override { std::cout << "Print from MI" << std::endl; }
	~MI() override { std::cout << "MI" << std::endl; }
};
int main()
{
	Base1 *pb1 = new MI;
	Base2 *pb2 = new MI;
	D1 *pd1 = new MI;
	D2 *pd2 = new MI;
	std::cout << "pb1 print..........." << std::endl;
	pb1->print();
	std::cout << "pd1 print..........." << std::endl;
	pd1->print();
	std::cout << "pd2 print..........." << std::endl;
	pd2->print();
	std::cout << "delete pb2..........." << std::endl;
	delete pb2;
	std::cout << "delete pd1..........." << std::endl;
	delete pd1;
	std::cout << "delete pd2..........." << std::endl;
	delete pd2;
}

Test:

pb1 print...........
Print from MI
pd1 print...........
Print from MI
pd2 print...........
Print from MI
delete pb2...........
MI
D2
Base2
D1
Base1
delete pd1...........
MI
D2
Base2
D1
Base1
delete pd2...........
MI
D2
Base2
D1
Base1

Exercise 18.26

Given the inheritance system (P716) shown above, why is the following call to print wrong? Modify the MI appropriately so that its call to print can be compiled and executed correctly.
MI mi;
mi.print(42);

#include <iostream>
#include <vector>
struct Base1 {
	void print(int) const {
		std::cout << "Base1 Print Used" << std::endl;
	};
protected:
	int ival;
	double dval;
	char cval;
private:
	int *id;
};
struct Base2 {
	void print(double) const;
protected:
	double fval;
private:
	double dval;
};
struct Derived : public Base1 {
	void print(std::string) const;
protected:
	std::string sval;
	double dval;
};
struct MI : public Derived, public Base2 {
	void print(std::vector<double>) {};
	void print(int x) {
		Base1::print(x);
	}
protected:
	int *ival;
	std::vector<double> dvec;
};
using namespace std;
int main()
{
	MI mi;
	mi.print(42);
	return 0;
}

Test: Base1 Print Used
There is no print version in MI that matches the integer parameter. If you just delete the print function in MI, there will be ambiguity between the derived version of print and the Base2 version. Therefore, you should overload the MI version of print() to get an int parameter.

Exercise 18.27

The inheritance system shown above is known, and it is assumed that a function named foo is added to MI:
int ival;
double dval;
void MI::foo(double foo){
int dval;
//The problem in the exercise happened here
}
(a) List names visible in MI::foo
(b) Is there a visible name that inherits multiple base types?
(c) Sum the dval members of Base1 and the dval members of Derived and assign them to the local instance of dval
(d) Assign the last value of MI::dvec to Base2::fval.
(e) Assign the cval inherited from Base1 to the first character of the sval inherited by Derived.

#include <iostream>
#include <vector>
struct Base1 {
	void print(int) const {
		std::cout << "Base1 Print Used" << std::endl;
	};
protected:
	int ival;
	double dval;
	char cval;
private:
	int *id;
};
struct Base2 {
	void print(double) const;
protected:
	double fval;
private:
	double dval;
};
struct Derived : public Base1 {
	void print(std::string) const;
protected:
	std::string sval = std::string(1, Base1::cval);//(e)
	double dval;
};
struct MI : public Derived, public Base2 {

	void print(std::vector<double>) {};
	void print(int x) {
		Base1::print(x);
	}

	int ival;
	double dval;

	void foo(double cval)
	{
		int dval;
		dval = Base1::dval + Derived::dval;//(c)
		Base2::fval = dvec.back() - 1;//(d)
		Derived::sval[0] = Base1::cval;//(e)
		std::cout << dval;
	}
protected:
	std::vector<double> dvec = { 9,8,7 };
};
int main()
{
	MI mi;
	mi.foo(1.5);
	return 0;
}

(a) All properties of MI derived classes are visible, except for private classes.
(b) Yes, any duplicate, non private name in the base class can be accessed in foo by adding a scope operator.
(c) , (d) and (e) as shown above.

Exercise 18.28

It is known that the following inheritance system exists. Which inherited members in the VMI class can be accessed directly without a prefix qualifier? Which must have finite identifiers to access?

The purpose of virtual inheritance is to make a class declare that it is willing to share its base class. The sub object of the shared base class is called and virtual base class. In this case, no matter how many times the virtual base class appears in the integration system, the derived class only contains the only shared virtual base class object.

struct Base{
    void bar(int); //It is public by default. No restricted access, no arg anywhere defined with int
protected:
    int ival;//It needs to be qualified. VMI will use Derived2::ival by default
};
struct Derived1 : virtual public Base{
    void bar(char);//Unconditional access, VMI was born in Derived1, which was born in Base.
    void foo(char);//You need to add a qualifier to convert parameters between two foos.
protected:
    char cval;//Qualifiers need to be added to prevent ambiguity with other cval s.
};

struct Derived2 : virtual public Base{
void foo(int);//You need to add a qualifier to convert parameters between two foos.
protected:
    int ival;//Access is not restricted.
    char cval;//Qualifiers need to be added to prevent ambiguity with other cval s.
};
class VMI : public Derived1, public Derived2 { };

Exercise 18.29

The following inheritance systems are known to exist
class Class {...};
class Base : public Class {...};
class D1 : virtual public Base {...};
class D2 : virtual public Base {...};
class MI : public D1, public D2 {...};
class Final : public MI, public Class {...};
(a )When a FInal Object, what is the order of execution of the constructor and destructor?
(b )In one Final Objects Base Part, several Class Part?
(c )Which of the following assignment operations will cause compilation errors?

(a) P721, the compiler checks the Base classes in order of declaration to determine whether they contain virtual Base classes. If so, first construct the virtual Base Class, and then construct other non virtual Base classes one by one in the order of declaration. Objects are destroyed in the reverse order of their construction. Construction order: Class, Base, D1, D2, MI, Class, FInal. The order of the structure is the opposite.
(b) One Base part and two Class parts.
(c)

#include <iostream>
using namespace std;
class Class {
public:
	Class() { cout << "Class() called" << endl; }
	~Class() { cout << "~Class() called" << endl; }
};
class Base : public Class {
public:
	Base() { cout << "Base() called" << endl; }
	~Base() { cout << "~Base() called" << endl; }
};
class D1 : virtual public Base {
public:
	D1() { cout << "D1() called" << endl; }
	~D1() { cout << "~D1() called" << endl; }
};
class D2 : virtual public Base {
public:
	D2() { cout << "D2() called" << endl; }
	~D2() { cout << "~D2() called" << endl; }
};
class MI : public D1, public D2 {
public:
	MI() { cout << "MI() called" << endl; }
	~MI() { cout << "~MI() called" << endl; }
};
class Final : public MI, public Class {
public:
	Final() { cout << "Final() called" << endl; }
	~Final() { cout << "~Final() called" << endl; }
};
int main(int argc, char const *argv[])
{
	Final final;
	Base *pb;
	Class *pc;
	MI *pmi;
	D2 *pd2;
	// pb = new Class; / / error. Class is the Base class of Base, while pb is the Base class. You cannot implicitly convert a Base class pointer to a derived class pointer.
	//error: invalid conversion from 'Class*' to 'Base *'
	// pc = new Final; / / error: 'class' is an ambient base of' final '
	//pmi = pb; / / error. pb is the Base class and MI is a subclass of Base. You cannot implicitly convert a Base class pointer to a derived class pointer.
	pd2 = pmi;//Pointers to derived classes can be converted to pointers to base classes.
	return 0;
}

Test:

Class() called
Base() called
D1() called
D2() called
MI() called
Class() called
Final() called
~Final() called
~Class() called
~MI() called
~D2() called
~D1() called
~Base() called
~Class() called

Exercise 18.30

Define a default constructor, a copy function, and a constructor that accepts int parameters in Base. These three constructors are defined in each derived class, and each constructor should initialize its Base part with its arguments.

class Class {};  
class Base :public Class {  
protected:  
    int ival;  
public:  
    Base() :ival(0),Class() {};  
    Base(const Base &b) = default;  
    Base(int a) :ival(a),Class() {}  
};  
class D1 :public virtual Base {  
public:  
    D1() :Base() {}  
    D1(const D1 &b) = default;  
    D1(int a) :Base(a) {}  
};  
class D2 :public virtual Base {  
public:  
    D2() :Base() {}  
    D2(const D2 &b) = default;  
    D2(int a) :Base(a) {}  
};  
class MI :public D1, public D2 {  
public:  
    MI() {}  
    MI(const MI &m) :Base(m), D1(m), D2(m) {}  
    MI(int i) :Base(i), D1(i), D2(i) {}  
};  
class Final :public MI, public Class {  
public:  
    Final() {}  
    Final(const Final &f) : Base(f), MI(f), Class() {}  
    Final(int i) : Base(i), Class() {}  
};  
76 original articles published, 44 praised, 10000 visitors+
Private letter follow

Tags: Lambda

Posted on Tue, 10 Mar 2020 01:27:47 -0700 by onlinegs