Looking at big design from small functions and small classes -- small talk C ++ (4)

Source: Internet
Author: User

[Mac 10.7.1 lion intel-based x64 gcc4.2.1 xcode4.2]

Everyone will say that because the books are written everywhere.

I don't like the truth. The truth is beyond the truth.

Q: What kind of code should I provide if I need to provide a module that computes two distinct elements?

A: Of course, it is not easy to think of elements as shaping, which may cause unavailability in many cases. In this case, templates become a good choice.

Q: Is the code similar to the following?

template<class T>T   max(T a, T b){    return a > b ? a : b;}

A: Of course, the logic of this Code is correct. However, there is no need to consider the element as an object. In this case, passing parameters may consume space and time savings.

Q: modify it as follows:

template<class T>T   max(T &a, T &b){    return a > b ? a : b;}

A: Based on this example, write the following test code:

#include <iostream>using namespace std;#define COUT_ENDL(str)      std::cout << #str << " is " << (str) << std::endl;namespace me{    template<class T>    T   max(T &a, T &b)    {        return a > b ? a : b;    }    }int main (int argc, const char * argv[]){    me::max(1, 2);    return 0;}

Unfortunately, a compilation error occurs:

No matching function for call to 'max'Candidate function [with T = int] not viable: no known conversion from 'int' to 'int &' for 1st argument

This means that parameter 1 cannot be converted to Int. This is understandable because the parameter form of Max is T &, which represents a variable passed in externally and may be modified internally. However, the integer literal 1 is passed in, which makes the compiler very difficult. Const reference is useful.

Q: Modify it again as follows:

#include <iostream>using namespace std;#define COUT_ENDL(str)      std::cout << #str << " is " << (str) << std::endl;namespace me{    template<class T>    T   max(const T &a, const T &b)    {        return a > b ? a : b;    }    }int main (int argc, const char * argv[]){    me::max(1, 2);    return 0;}

Compilation, OK.

A: The return value is of the T type. It may need to be constructed. Why not change the return value to the const T & type?

Q: Is the function return value const T & type feasible?

A: First, the return value of a function can only be used as the right value, so it is feasible to use T & as the return value type. Similarly, using const modification still does not cause problems for values that may exist in the outside world, a const object can be assigned a value to a non-const object. Furthermore, if the returned result is input A or B, local references are not returned.

Q: The modification is as follows:

#include <iostream>using namespace std;#define COUT_ENDL(str)      std::cout << #str << " is " << (str) << std::endl;namespace me{    template<class T>    const T& max(const T& a, const T& b)    {        return a > b ? a : b;    }    }int main (int argc, const char * argv[]){    int ret = me::max(1, 2);    return 0;}

Compilation is fine.

A: If both A and B are objects, this function can be compiled on the premise that T has a function with an overload greater than the operator. However, in STL, many codes use the operators whose values are less than or equal to the value of the overload as the standard by default, that is, >,<,=, >=, <= ,! = Among these operators, you only need to overload the <and = operators to implement other forms with existing overloading. Therefore, the code can be modified to use the <operator.

Q:

#include <iostream>using namespace std;#define COUT_ENDL(str)      std::cout << #str << " is " << (str) << std::endl;namespace me{    template<class T>    const T& max(const T& a, const T& b)    {        return a < b ? b : a;    }    }int main (int argc, const char * argv[]){    int ret = me::max(1, 2);    COUT_ENDL(ret)    return 0;}

A: For the max function, its implementation is very simple. using inline is a good choice.

#include <iostream>using namespace std;#define COUT_ENDL(str)      std::cout << #str << " is " << (str) << std::endl;namespace me{    template<class T>    inline     const T& max(const T& a, const T& b)    {        return a < b ? b : a;    }    }int main (int argc, const char * argv[]){    int ret = me::max(1, 2);    COUT_ENDL(ret)    return 0;}

Q: What is the max function code in STL that comes with the MAC system?

A: as follows:

  template<typename _Tp>    inline const _Tp&    max(const _Tp& __a, const _Tp& __b)    {      // concept requirements      __glibcxx_function_requires(_LessThanComparableConcept<_Tp>)      //return  __a < __b ? __b : __a;      if (__a < __b)return __b;      return __a;    }

Q: As I just mentioned, the operators <and = can be reloaded for objects. Other Relational operators can be implemented through them. When writing code, do each class still need to implement several other types of overload functions (although only coding <and = Operator operation deformation?

A: Because the operations of other operators are similar, you can abstract them all and implement them using templates. As follows:

    template <class _Tp>      inline bool      operator!=(const _Tp& __x, const _Tp& __y)      { return !(__x == __y); }    template <class _Tp>      inline bool      operator>(const _Tp& __x, const _Tp& __y)      { return __y < __x; }    template <class _Tp>      inline bool      operator<=(const _Tp& __x, const _Tp& __y)      { return !(__y < __x); }    template <class _Tp>      inline bool      operator>=(const _Tp& __x, const _Tp& __y)      { return !(__x < __y); }

Of course, you do not need to write the code again. You can refer to the implementation code in the rel_ops namespace in the STD namespace.

Q: What are the benefits of the initialization list in the class constructor?

A: Example:

#include 
 
  using namespace std;#define COUT_ENDL(str)      std::cout << #str << " is " << (str) << std::endl;
 class Student{public:    Student(int age)    {        _age = age;    }    ~Student(){ }    public:    int age() { return _age; }    int age() const { return _age; }    private:    int _age;};int main (int argc, const char * argv[]){    Student s(21);    return 0;}

Compile to get the compilation code of the student constructor:

0x0000000100000cdc <_ZN7StudentC1Ei+0>:push   %rbp0x0000000100000cdd <_ZN7StudentC1Ei+1>:mov    %rsp,%rbp0x0000000100000ce0 <_ZN7StudentC1Ei+4>:mov    %rdi,-0x8(%rbp)0x0000000100000ce4 <_ZN7StudentC1Ei+8>:mov    %esi,-0xc(%rbp)0x0000000100000ce7 <_ZN7StudentC1Ei+11>:mov    -0x8(%rbp),%rax0x0000000100000ceb <_ZN7StudentC1Ei+15>:mov    -0xc(%rbp),%ecx0x0000000100000cee <_ZN7StudentC1Ei+18>:mov    %ecx,(%rax)0x0000000100000cf0 <_ZN7StudentC1Ei+20>:pop    %rbp0x0000000100000cf1 <_ZN7StudentC1Ei+21>:retq 

Change the student class constructor to the following:

Student(int age):_age(age) { }

Compile again to obtain its Assembly Form:

0x0000000100000cdc <_ZN7StudentC1Ei+0>:push   %rbp0x0000000100000cdd <_ZN7StudentC1Ei+1>:mov    %rsp,%rbp0x0000000100000ce0 <_ZN7StudentC1Ei+4>:mov    %rdi,-0x8(%rbp)0x0000000100000ce4 <_ZN7StudentC1Ei+8>:mov    %esi,-0xc(%rbp)0x0000000100000ce7 <_ZN7StudentC1Ei+11>:mov    -0x8(%rbp),%rax0x0000000100000ceb <_ZN7StudentC1Ei+15>:mov    -0xc(%rbp),%ecx0x0000000100000cee <_ZN7StudentC1Ei+18>:mov    %ecx,(%rax)0x0000000100000cf0 <_ZN7StudentC1Ei+20>:pop    %rbp0x0000000100000cf1 <_ZN7StudentC1Ei+21>:retq  

The two are consistent, indicating that the initialization list is not mysterious. Of course, this is a simple type.

Q: Are the complex types still consistent?

A: The following code:

#include <iostream>using namespace std;#define COUT_ENDL(str)      std::cout << #str << " is " << (str) << std::endl;class Person{public:    Person() { }    Person(char sex):_sex(sex)    {        std::cout << "enter Person construct function..." << std::endl;    }    ~Person() { }    Person(const Person& p)    {        std::cout << "enter Person copy construct function..." << std::endl;        _sex = p._sex;    }        Person& operator=(const Person& p)    {        std::cout << "enter Person assign construct function..." << std::endl;        if(&p != this)        {            _sex = p._sex;        }        return *this;    }    private:    char _sex;};class Student{public:    Student(int age, Person &p):_age(age), _p(p)     {            }    ~Student(){ }    public:    int age() { return _age; }    int age() const { return _age; }    private:    int     _age;    Person  _p;};int main (int argc, const char * argv[]){    Person  p('m');    Student s(21, p);    return 0;}

Running result:

We can see that except for the p object to call the constructor separately, the s Object Construction only calls the copy constructor for the person class.

Q: What if the initialization list is not used?

A: Modify the Code as follows:

#include <iostream>using namespace std;#define COUT_ENDL(str)      std::cout << #str << " is " << (str) << std::endl;class Person{public:    Person() { }    Person(char sex):_sex(sex)    {        std::cout << "enter Person construct function..." << std::endl;    }    ~Person() { }    Person(const Person& p)    {        std::cout << "enter Person copy construct function..." << std::endl;        _sex = p._sex;    }        Person& operator=(const Person& p)    {        std::cout << "enter Person assign construct function..." << std::endl;        if(&p != this)        {            _sex = p._sex;        }        return *this;    }    private:    char _sex;};class Student{public:    Student(int age, Person &p):_age(age)    {        _p = p;    }    ~Student(){ }    public:    int age() { return _age; }    int age() const { return _age; }    private:    int     _age;    Person  _p;};int main (int argc, const char * argv[]){    Person  p('m');    Student s(21, p);    return 0;}

The running result is as follows:

The difference between the constructor and the preceding execution result is that it executes one more constructor. This is also the reason that may lead to lower efficiency. Similarly, if the constructor parameter is not referenced by the person parameter and is of the person type, there is a similar gap. At the same time, we can see the difference between constructing sub-objects on the initialization list and constructing sub-objects within the constructor.

Q: Is the default constructor of the person constructor unnecessary if the initialization list is used?

A: Yes. The following code does not use the initialization list method. The default constructor of person is not written, leading to a compilation error:

#include <iostream>using namespace std;#define COUT_ENDL(str)      std::cout << #str << " is " << (str) << std::endl;class Person{public:    Person(char sex):_sex(sex)    {        std::cout << "enter Person construct function..." << std::endl;    }    ~Person() { }    Person(const Person& p)    {        std::cout << "enter Person copy construct function..." << std::endl;        _sex = p._sex;    }        Person& operator=(const Person& p)    {        std::cout << "enter Person assign construct function..." << std::endl;        if(&p != this)        {            _sex = p._sex;        }        return *this;    }    private:    char _sex;};class Student{public:    Student(int age, Person &p):_age(age)    {        _p = p;    }    ~Student(){ }    public:    int age() { return _age; }    int age() const { return _age; }    private:    int     _age;    Person  _p;};int main (int argc, const char * argv[]){    Person  p('m');    Student s(21, p);    return 0;}

Compilation error:

No matching function for call to 'Person::Person()'

Q: Why is there no return value for the constructor?

A: The problem is the returned value and what is returned. If an integer is returned, such as person P = person ('M'); and so on, it is similar to assigning an integer to the variable P. How is this reasonable?

Q: Why is there no return value for the Destructor?

A: If it is a partial object of a function, C ++ specifies that the final structure of the partial object is generated by the compiler. Since the compiler generates the corresponding code, so how can programmers get the return value?

Q: Why is it not recommended to throw an exception in the constructor?

A: The execution path changes due to an exception mechanism, and the final destructor may not be executed, resulting in Memory leakage and other problems. The following code:

#include <iostream>using namespace std;#define COUT_ENDL(str)      std::cout << #str << " is " << (str) << std::endl;class Test{public:    Test()    {        std::cout << "Test is called..." << std::endl;        throw 1;    }        ~Test()    {        std::cout << "~Test is called..." << std::endl;    }};int main (int argc, const char * argv[]){    Test t;    return 0;}

Running result:

It can be seen that the Destructor is not executed normally, but an exception prompt is output. Of course, you can use auto_ptr to solve this problem.

Q: Why is it not recommended to directly throw an exception for destructor?

A: This also comes from the status of the object after an exception is thrown. Whether the object can be ignored or not, and the operation will continue later. These uncertainties also make the standard more uncertain for programmers. Of course, different compilers do not directly call terminate for exceptions thrown by destructor, as shown in the following test code:

#include <iostream>using namespace std;#define COUT_ENDL(str)      std::cout << #str << " is " << (str) << std::endl;class Test{public:    Test()    {        std::cout << "Test is called..." << std::endl;    }        ~Test()    {        std::cout << "~Test is called..." << std::endl;        throw 1;    }};int main (int argc, const char * argv[]){    try    {        Test *t = new Test;        delete t;    }    catch(...)    {        std::cout << "catch exception..." << std::endl;    }        int i = 1;    COUT_ENDL(i)        return 0;}

Running result:

It runs the output I code.

Q: Where can I handle the exceptions that may be thrown in constructor and destructor?

A: In fact, after the construction is complete, other functions can judge the processing of objects to determine whether a problem has occurred. Where the Destructor may be abnormal, the flag information can also be used to record the transfer to the outer layer.

Xichen

2012-6-1 13:16:44

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.