C + + programming and object model design
Conversion function
#include <iostream> class Fraction { public: Fraction(int num, int den = 1) : m_numerator(num), m_denominator(den) {} operator double() const { return ((double)m_numerator/m_denominator); } private: int m_numerator; // molecule int m_denominator; // denominator }; int main() { Fraction f(3, 5); double d = 4+f; std::cout << d << std::endl; }
Non explicit one argument constructor
Automatically convert other types to the current type
#include <iostream> class Fraction { public: Fraction(int num, int den = 1) : m_numerator(num), m_denominator(den) {} //operator double() const { // return ((double)m_numerator/m_denominator); //} Fraction operator +(const Fraction& f) const { return ((double)m_numerator/m_denominator); } private: int m_numerator; // molecule int m_denominator; // denominator }; int main() { Fraction f(3, 5); Fraction d = f+4; }
If you add
#include <iostream> class Fraction { public: explicit Fraction(int num, int den = 1) : m_numerator(num), m_denominator(den) {} Fraction operator +(const Fraction& f) const { return ((double)m_numerator/m_denominator); } private: int m_numerator; // molecule int m_denominator; // denominator }; int main() { Fraction f(3, 5); Fraction d = f+4; }
Will report an error
non-expllicit.cpp: In member function'Fraction Fraction::operator+(const Fraction&) const'in: non-expllicit.cpp:8:32: Error: could not convert '((double)(int)((const Fraction*)this)->Fraction::m_numerator / (double)(int)((const Fraction*)this)->Fraction::m_denominator)' from 'double' to 'Fraction' 8 | return ((double)m_numerator/m_denominator); | ~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~ | | | double
pointer-like classes
Smart pointer
template<calass T> class shared_ptr { public: T& operator*() const { return *px; } T* operator->() const { return px; } share_ptr(T* p) : px(p) {} private: T* px; long* pn; }
iterator
reference operator*() const { return (*node).data; } pointer operator->() const { return &(operator*()); }
function-like classes
Overload ()
namespace
#include <iostream> namespace fraction { class Fraction { public: Fraction(int num, int den = 1) : m_numerator(num), m_denominator(den) {} operator double() const { return ((double)m_numerator / m_denominator); } private: int m_numerator; // molecule int m_denominator; // denominator }; } int main(int argc, char *argv[]) { fraction::Fraction f(3, 5); double d = 4+f; std::cout << d << std::endl; return 0; }
Member template
namespace pair_tmpl { template<class T1, class T2> struct pair{ typedef T1 first_type; typedef T2 second_type; T1 first; T2 second; pair() : first(T1()), second(T2()) {} pair(const T1& a, const T2& b) : first(a), second(b) {} // Member template template <class U1, class U2> pair(const pair<U1, U2>& p) : first(p.first), second(p.second) {} }; class Base1 {}; class Sub1 : public Base1 {}; class Base2 {}; class Sub2 : public Base2 {}; } int main(int argc, char *argv[]) { pair_tmpl::pair<pair_tmpl::Sub1, pair_tmpl::Sub2> p; pair_tmpl::pair<pair_tmpl::Base1, pair_tmpl::Base2> p2(p); return 0; }
template<typename _Tp> class shared_ptr : public __shared_ptr<_TP> { ... template<typename _Tp1> explicit shared_ptr(_Tp1* __p): __shared_ptr<_Tp>(__p) {} ... } Base1 * ptr = new Sub1; shared_ptr<Base1> sptr(new Sub1);
Specialization template specialization
In fact, templates are generalization
On the basis of generalization, different treatments are made according to different types
namespace specialization { template <class Key> struct hash {}; template <> struct hash<char> { std::size_t operator()(char x) const { return x; } }; template <> struct hash<int> { std::size_t operator()(char x) const { return x; } }; template <> struct hash<long> { std::size_t operator()(char x) const { return x; } }; } // namespace specialization
partial template specialization
- Partial specialization of number
template<typename T, typename Alloc=...> class vector { ... }; template<typename Alloc=...> class vector<bool, Alloc> { ... };
- Partial specialization of range
template <typename T> class C {}; template <typename T> class C<T *> {}; C<string> obj1; C<string*> obj2;
Template parameters
template <typename T, template <typename> class SmartPtr> class XCls { private: SmartPtr<T> sp; public: XCls() : sp(new T) {} };
Three themes
Variable number of template parameters
namespace variadic { void print() {} // The last thing I want to do when I call a parameter. template <typename T, typename... Types> void print(const T& firstArg, const Types&... args) { std::cout << firstArg << std::endl; print(args...); } }
- sizeof...(args)
namespace variadic { void print() {} template <typename T, typename... Types> void print(const T& firstArg, const Types&... args) { printf("%d\n", sizeof...(args)); std::cout << firstArg << std::endl; print(args...); } }
auto
list<string> c; ... list<string>::iterator ite; ite = find(c.begin(), c.end(), target); auto ite = find(c.begin(), c.end()m target);
ranged-base for
for (int i : {2, 3, 4, 5, 6, 7, 8, 9}) { cout << i << endl; } vector<double> vec; for (auto elem : vec) { cout << elem << endl; } for (auto& elem : vec) { elem *= 3; }
Reference (important)
namespace reference { typedef struct Stag {int a, b, c, d;} S; } int main(int argc, char *argv[]) { double x = 0; double* p = &x; double& r = x; std::cout << sizeof(x) << std::endl; std::cout << sizeof(p) << std::endl; std::cout << sizeof(r) << std::endl; std::cout << x << std::endl; std::cout << &x << std::endl; std::cout << p << std::endl; std::cout << *p << std::endl; std::cout << &p << std::endl; std::cout << r << std::endl; std::cout << &r << std::endl; reference::S s; reference::S& rs = s; std::cout << sizeof(s) << std::endl; std::cout << sizeof(rs) << std::endl; std::cout << &s << std::endl; std::cout << &rs << std::endl; return 0; }
8 8 8 0 0x7fffd7f79b10 0x7fffd7f79b10 0 0x7fffd7f79b08 0 0x7fffd7f79b10 16 16 0x7fffd7f79af8 0x7fffd7f79af8
There is a good saying that reference is a beautiful pointer
Purpose of reference
- Note that const is part of the function signature
Composition & Inheritance & Construction and Deconstruction in delegation
combination
- Structure from inside to outside
Container::Container() : Component() {}
- Deconstruction from outside to inside
Container::~Container() { ~Component(); }
Note that the compiler can only help us call the default constructor. If necessary, we need to implement it ourselves
inherit
Construction and deconstruction under inheritance + combination relationship
- Structure from inside to outside
The constructor of the subclass calls the default constructor of the parent class first, then calls the default constructor of the component, and finally executes itself.
Sub::Sub() : Base(), Component() {}
- Deconstruction from outside to inside
The destructor of the subclass first destruct itself, invokes the destructor of the component, and finally calls the constructor of the parent class.
Sub::~Sub() { // Do something; ~Component(); ~Base(); }
About vptr and vtbl
namespace Virtual { class A{ public: virtual void vfunc1(); virtual void vfunc2(); void func1(); void func2(); private: int m_data1, m_data2; }; class B : public A{ public: virtual void vfunc1(); void func2(); private: int m_data3; }; class C : public B{ public: virtual void vfunc1(); void func2(); private: int m_data_4; }; }
(*(p->vptr)[n])(p) (* p->vptr[n])(p)
Static binding
call ox.....
Dynamic binding
- Pointer call
- Upward transformation
- A virtual function is called
(*(p->vptr)[n])(p)
About this
this is the address of the current object
Look at an example. This is the same as the virtual mechanism above
class CDocument { public: virtual Serialize(); }; // An application framework CDocument::OnFileOpen() { ... Serialize(); // A virtual function ... } // application program class CMyDoc: public CDocument { virtual Serialize() { ... } } int main() { CMyDoc myDoc; ... myDoc.OnFileOpen(); // Call parent method // Equivalent to cdocument:: onfileopen (& MyDoc); this == &myDoc // Because it is inheritance, there is actually a parent class (calling the constructor) in the child class // The pointer passed in cdocument:: onfileopen (& MyDoc) is a parent class that transforms upward (child - > parent) // Then, when it comes to Serialize(), because it is a member function, it is equivalent to this - > Serialize() = = & MyDoc - > Serialize() // Then it is equivalent to (* (this - > VPTR) [n]) (this), which is dynamic binding }
About dynamic binding (see assembly)
B b; A a = (A)b; a.vfunc1(); // This sentence is a static binding
A* pa = new B; ps->vfunc1(); pa = &b; pa->vfunc1();
Talk about const
const object(data members cannot be changed) | Non const object (data members) can be changed | |
---|---|---|
Const member functions (ensure that data members are not modified) | T | T |
Non const member functions (data members are not guaranteed to be modified) | F | T |
When const and non const versions of member functions exist at the same time, const object can only call const version, and non const object can only call non const version
Design in std::String
When const and non const versions of member functions exist at the same time, const object can only call const version, and non const object can only call non const version
class template std::basic_string<...>There are the following two member functions: charT operator[] (size_typr pos) const { // Copy on write is not considered } reference operator[] (size_type pos) { // Copy on write must be considered }
About New Delete
No virtual function
#include <iostream> #include <stddef.h> #include <stdlib.h> #include <string> class Foo { public: int _id; long _data; std::string _str; public: Foo() : _id(0) { std::cout << "call default ctor.this=" << this << "id=" << _id << std::endl; } Foo(int i) : _id(i) { std::cout << "call default ctor.this=" << this << "id=" << _id << std::endl; } //Virtual ~ foo() {STD:: cout < < call dTOR. This = "< < this < < id =" < < u id < < STD:: endl;} ~Foo() { std::cout << "call dtor.this=" << this << "id=" << _id << std::endl; } static void *operator new(size_t size); static void operator delete(void *pdead, size_t size); static void *operator new[](size_t size); static void operator delete[](void *pdead, size_t size); }; void *Foo::operator new(size_t size) { std::cout << "call new() size=" << size <<std::endl; std::cout << "Apply for memory first\n"; Foo *p = (Foo *)malloc(size); return p; } void Foo::operator delete(void *pdead, size_t size) { std::cout << "call delete()\n"; std::cout << "free Memory\n"; free(pdead); } void *Foo::operator new[](size_t size) { std::cout << "call new[]() size=" << size <<std::endl; std::cout << "Apply for memory first\n"; Foo *p = (Foo *)malloc(size); return p; } void Foo::operator delete[](void *pdead, size_t size) { std::cout << "call delete[]()\n"; std::cout << "free Memory\n"; free(pdead); } int main(int argc, char *argv[]) { std::cout << sizeof(Foo) << std::endl; Foo *p = new Foo(7); delete p; Foo *pArray = new Foo[5]; delete[] pArray; return 0; }
result
48 call new() size=48 Apply for memory first call default ctor.this=0x55ee164252c0id=7 call dtor.this=0x55ee164252c0id=7 call delete() free Memory call new[]() size=248 Apply for memory first call default ctor.this=0x55ee16425308id=0 call default ctor.this=0x55ee16425338id=0 call default ctor.this=0x55ee16425368id=0 call default ctor.this=0x55ee16425398id=0 call default ctor.this=0x55ee164253c8id=0 call dtor.this=0x55ee164253c8id=0 call dtor.this=0x55ee16425398id=0 call dtor.this=0x55ee16425368id=0 call dtor.this=0x55ee16425338id=0 call dtor.this=0x55ee16425308id=0 call delete[]() free Memory
Virtual function
#include <iostream> #include <stddef.h> #include <stdlib.h> #include <string> class foo { public: int _id; long _data; std::string _str; public: foo() : _id(0) { std::cout << "call default ctor.this=" << this << "id=" << _id << std::endl; } foo(int i) : _id(i) { std::cout << "call default ctor.this=" << this << "id=" << _id << std::endl; } virtual ~foo() { std::cout << "call dtor.this=" << this << "id=" << _id << std::endl; } //~Foo() {STD:: cout < < call dTOR. This = "< < this < < id =" < < u id < < STD:: endl;} static void *operator new(size_t size); static void operator delete(void *pdead, size_t size); static void *operator new[](size_t size); static void operator delete[](void *pdead, size_t size); }; void *foo::operator new(size_t size) { std::cout << "call new() size=" << size <<std::endl; std::cout << "Apply for memory first\n"; foo *p = (foo *)malloc(size); return p; } void foo::operator delete(void *pdead, size_t size) { std::cout << "call delete()\n"; std::cout << "free Memory\n"; free(pdead); } void *foo::operator new[](size_t size) { std::cout << "call new[]() size=" << size <<std::endl; std::cout << "Apply for memory first\n"; foo *p = (foo *)malloc(size); return p; } void foo::operator delete[](void *pdead, size_t size) { std::cout << "call delete[]()\n"; std::cout << "free Memory\n"; free(pdead); } int main(int argc, char *argv[]) { std::cout << sizeof(foo) << std::endl; foo *p = new foo(7); delete p; foo *parray = new foo[5]; delete[] parray; return 0; }
result
56 call new() size=56 Apply for memory first call default ctor.this=0x55d683a2f2c0id=7 call dtor.this=0x55d683a2f2c0id=7 call delete() free Memory call new[]() size=288 Apply for memory first call default ctor.this=0x55d683a2f308id=0 call default ctor.this=0x55d683a2f340id=0 call default ctor.this=0x55d683a2f378id=0 call default ctor.this=0x55d683a2f3b0id=0 call default ctor.this=0x55d683a2f3e8id=0 call dtor.this=0x55d683a2f3e8id=0 call dtor.this=0x55d683a2f3b0id=0 call dtor.this=0x55d683a2f378id=0 call dtor.this=0x55d683a2f340id=0 call dtor.this=0x55d683a2f308id=0 call delete[]() free Memory