改善程序与设计的55个具体做法
C++是一个多重范型编程语言,支持过程形式、面向对象形式、函数形式、泛型形式、元编程形式,C++高效编程守则视状况而变化,取决于使用C++哪一个部分,在合适的场景选择使用合适的功能
C开发中以前都在使用宏定义define,但是往往难以维护而且难以调试
#define ASPECT_RATIO 1.653
//使用const替换
const double AspectRatio = 1.653;
const的底层const与顶层const要知道
const char* const authorName = "Scott Meyers";//字符串用const代替宏
//cpp等推荐使用string,更加抽象
const std::string authorName("Scott Meyers");
对于类内的可以使用静态成员变量
class A{
private:
static const int Num = 5;//常量声明式
int scores[Num];
};
如果不使用A::Num的地址,那么只使用声明式即可,如果要用地址则需要定义式在源文件中
const int A::Num;//Num定义,声明时已经提供初值,定义式不再需要提供初值
//如果编译器不支持声明式初始化,则要在定义式提供初值
类内的enum更像define,不能取值
class A
{
public:
enum
{
= 4
Num };
int arr[Num];
};
int main(int argc, char **argv)
{
<< A::Num << endl; // 4
cout return 0;
}
关于define写函数形式的一些问题,尽量使用inline,一般inline函数写在头文件中,因为源文件编译时需要将函数展开
#define MAX(a, b) (a) > (b) ? (a) : (b)
int main(int argc, char **argv)
{
int n = MAX(1, 2);
<< n << endl; // 2
cout // int n1 = MAX(n++, ++n); 这种问题容易混乱
return 0;
}
//尽可能使用inline函数,也可以使用模板进行扩展
//函数也能展开,而且利于开发维护
template <typename T>
inline T mymax(const T &a, const T &b)
{
return a > b ? a : b;
}
const有顶层const(一般为指针不能修改)与底层const(数据不能修改),
char greeting[] = "hello";
char *p = greeting;//none-const pointer,non-const data
const char* p =greeting;//non-const pointer,const data
char* const p = greeting;//const pointer,non-const data
const char* const p = greeting;//const pointer,const data
有两种形式的表达的意思是相同的,都是data const
void func(char const *p);
void func(const char*p);
函数返回常量值的作用
class A
{
public:
(const int &n) : num(n)
A{
}
int num;
const A operator*(const A &o)
{
return A(this->num * o.num);
}
};
int main(int argc, char **argv)
{
(1);
A a(2);
A b= a * b;
A c //(a * b) = 3; // 错误:操作数类型为: const A = int,如果返回的不是const值则不报错
<< c.num << endl; // 2
cout return 0;
}
const成员函数
class A
{
public:
static const int num{9};
char arr[num] = {0};
const char &operator[](std::size_t position) const
{
return arr[position];
}
char &operator[](std::size_t position)
{
return arr[position];
}
};
int main(int argc, char **argv)
{
;
A aconst A b;
<< a[0] << endl; // 调用char &A::operator[]
cout << b[0] << endl; // 调用 const char &A::operator[]
cout // b[0] = '1'; 错误
[0] = 'a';
a<< a[0] << endl; // a
cout return 0;
}
在const和non-const成员函数中避免重复,可以让non-const调用const成员函数
class A
{
public:
static const int num{9};
char arr[num] = {0};
const char &operator[](std::size_t position) const
{
//...
// ...
return arr[position];
}
char &operator[](std::size_t position)
{
return const_cast<char &>(static_cast<const A &>(*this)[position]);
}
};
int n;
<< n << endl; cout
会输出什么,大部分都会说0,但是不一定,有随机性,不能相信机器与编译器,加上个初始值不会杀了你
为什么要使用初始化列表,而不是在构造函数内赋值
class A
{
public:
()
A{
<< "A()" << endl;
cout }
(const int &n)
A{
<< "A(const int &n)" << endl;
cout }
const A &operator=(const int &n)
{
<< "const A &operator=(const int &n)" << endl;
cout return *this;
}
};
class B
{
public:
()
B{
= 1; // 这是赋值不是初始化
a }
;
A a};
int main(int argc, char **argv)
{
;
B b// A()
// const A &operator=(const int &n)
return 0;
}
如果使用构造函数列表,It’s fucking cool.特别注意的是初始化列表为什么要与在类内声明的顺序相同,这是因为它们构造的现后顺序并不取决于在初始化列表中的顺序而是在类内声明的顺序所以我们写代码直接把二者顺序同步好了。
class B
{
public:
() : a(1)
B{
}
;
A a};
int main(int argc, char **argv)
{
;
B b// A(const int &n)
return 0;
}
什么是local-static对象和non-local static对象,栈内存与堆内存对象都不是static对象。像全局对象、定义在命名空间作用域内的、在class内的、在函数内的、以及在源文件作用域内的被声明为static的对象。其中在函数内的为local-static其他为non-local static。程序结束时static会被自动销毁,析构函数在main返回前调用
可能有时会使用extern访问在其他源文件定义的对象,如果一个源文件中某个non-local static对象初始化时用到了另一个源文件中的non-local static对象,可能会出现赋值操作右边的变量没有初始化过的情况,因为C++中:对于“定义于不同源文件内的non-local static对象”的初始化次序并无明确定义
//mian.cpp
extern int n;
int n1=n;
//main1.cpp
int n;
怎样解决这一问题,推荐使用local static代替non-local static
//main.cpp
int n1=n();
//main1.cpp
int& n(){
static int v=100;
return v;
}
上面例子可能还不清楚看下面这个
//main.cpp
#include <iostream>
#include "main1.h"
#include "main2.h"
using namespace std;
int main(int argc, char **argv)
{
return 0;
}
//main1.h
#pragma once
class main1
{
public:
();
main1};
//main1.cpp
#include "main1.h"
#include <iostream>
;
main1 main1Object
::main1()
main1{
std::cout << "main1" << std::endl;
}
//main2.h
#pragma once
#include "main1.h"
class main2
{
private:
/* data */
public:
(/* args */);
main2};
//main2.cpp
#include "main2.h"
#include <iostream>
;
main2 main2Object
::main2(/* args */)
main2{
std::cout << "main2" << std::endl;
}
请问main1和main2谁先输出,答案是不确定的,所以总之记住全局变量之间不要互相引用初始化,特别是在不同源文件中的不同全局变量。
@DESKTOP-QDLGRDB:/mnt/c/Users/gaowanlu/Desktop/MyProject/note/testcode$ g++ -c main1.cpp
gaowanlu@DESKTOP-QDLGRDB:/mnt/c/Users/gaowanlu/Desktop/MyProject/note/testcode$ g++ -c main2.cpp
gaowanlu@DESKTOP-QDLGRDB:/mnt/c/Users/gaowanlu/Desktop/MyProject/note/testcode$ g++ -c main.cpp
gaowanlu@DESKTOP-QDLGRDB:/mnt/c/Users/gaowanlu/Desktop/MyProject/note/testcode$ g++ main.o main1.o main2.o -o main.exe
gaowanlu@DESKTOP-QDLGRDB:/mnt/c/Users/gaowanlu/Desktop/MyProject/note/testcode$ ./main.exe
gaowanlu
main1
main2@DESKTOP-QDLGRDB:/mnt/c/Users/gaowanlu/Desktop/MyProject/note/testcode$ g++ main.o main2.o main1.o -o main.exe
gaowanlu@DESKTOP-QDLGRDB:/mnt/c/Users/gaowanlu/Desktop/MyProject/note/testcode$ ./main.exe
gaowanlu
main2
main1@DESKTOP-QDLGRDB:/mnt/c/Users/gaowanlu/Desktop/MyProject/note/testcode$ gaowanlu
还心存执念,那你循环引用下,初始化肯定有问题吧,n1在main.cpp,n2在main1.cpp,n1用n2初始化,n2用n1初始化。这样虽然能编译,能运行,但它们的初始化确实有问题。
默认生成这些函数是C++的基础知识,应该问题不大,当程序中使用这些函数时编译器才会生成,如果自己声明了自定义的相关函数则编译器不再自动生成默认的对应函数
class A
{
public:
() {}
A~A() {}
(const A &a)
A{
this->num = a.num;
}
&operator=(const A &a)
A {
this->num = a.num;
return *this;
}
int num;
};
//写为private,只声明不定义
class A
{
public:
() {}
A~A() {}
private:
(const A &a); // 只声明不定义
A&operator=(const A &a);
A };
//使用delete关键词
class B
{
public:
() {}
B~B() {}
(const B &b) = delete;
B&operator=(const B &b) = delete;
B };
int main(int argc, char **argv)
{
;
A a;
A b// a = b; 错误
return 0;
}
还可以使用Uncopyable基类的方式,在基类进行拷贝构造和赋值时,会先执行基类的相关函数
class A
{
public:
() {}
A(const A &a)
A{
<< "A(const A&a)" << endl;
cout }
&operator=(const A &a)
A {
<< "A& operator=(const A&a)" << endl;
cout return *this;
}
virtual ~A() = default;
};
class B : public A
{
public:
() {}
B(const B &b) : A(b)
B{
<< "B(const B&b)" << endl;
cout }
&operator=(const B &b)
B {
if (&b != this)
{
::operator=(b);
A}
<< "B &operator=(const B &b)" << endl;
cout return *this;
}
~B() = default;
};
int main(int argc, char **argv)
{
;
B b1= b1;
B b2 // A(const A&a)
// B(const B &b)
return 0;
}
那么就可以写一个Uncopyable基类
class A
{
public:
() {}
Avirtual ~A() = default;
private:
(const A &a);
A&operator=(const A &a);
A };
class B : public A
{
public:
() {}
B~B() = default;
// 理应当自动生成拷贝构造和赋值操作函数,但是由于不能访问基类部分,所以不能自动生成
};
int main(int argc, char **argv)
{
;
B b1// B b2 = b1;
// 无法引用 函数 "B::B(const B &)" (已隐式声明) -- 它是已删除的函数
return 0;
}
先看以下有什么搞人的事情,深入理解此部分要对虚函数表以及C++多态机制有一定了解,下面的代码只执行了基类的析构函数只是释放了基类中buffer的动态内存,而派生类部分内存泄露,这是因为A*a
,a被程序认为其对象只是一个A,而不是B,如果将基类析构函数改为virtual的,那么会向下找,找到~B执行,然后再向上执行如果虚函数有定义的话
class A
{
public:
() : buffer(new char[10])
A{
}
~A()
{
<< "~A()" << endl;
cout delete buffer;
}
private:
char *buffer;
};
class B : public A
{
public:
() : buffer(new char[10])
B{
}
~B()
{
<< "~B()" << endl;
cout delete buffer;
}
private:
char *buffer;
};
int main(int argc, char **argv)
{
*a = new B;
A delete a;
//~A()
return 0;
}
所以要修改为这样,即可
class A
{
public:
() : buffer(new char[10])
A{
}
virtual ~A()
{
<< "~A()" << endl;
cout delete buffer;
}
private:
char *buffer;
};
如果想让基类为抽象类,可以改为纯虚函数,与前面不同的时拥有纯虚函数的类为抽象类不允许实例化,纯虚函数不用定义。而虚函数是需要有定义的。
class A
{
public:
() {}
Avirtual ~A() = 0;
};
::~A() {}
A
class B : public A
{
};
int main(int argc, char **argv)
{
// A a; 错误A为抽象类型
;
B breturn 0;
}
例如以下情况
void freeA()
{
throw runtime_error("freeA() error");
}
class A
{
public:
() {}
A~A()
{
try
{
();
freeA}
catch (...)
{
// std::abort();//生成coredump结束
// 或者处理异常
//...
}
}
};
int main(int argc, char **argv)
{
*a = new A;
A delete a;
return 0;
}
如果外部需要对某些在析构函数内的产生的异常进行操作等,应该提供新的方法,缩减析构函数内容
void freeA()
{
throw runtime_error("freeA() error");
}
class A
{
public:
() {}
A~A()
{
if (!freeAed)
{
try
{
();
freeA}
catch (...)
{
// std::abort();//生成coredump结束
// 或者处理异常
//...
}
}
}
void freeA()
{
::freeA();
= true;
freeAed }
private:
bool freeAed = {false};
};
int main(int argc, char **argv)
{
*a = new A;
A try
{
->freeA();
a}
catch (const runtime_error &e)
{
<< e.what() << endl;
cout }
delete a;
return 0;
}
1、构造函数中调用虚函数:
当在基类的构造函数中调用虚函数时,由于派生类的构造函数尚未执行,派生类对象的派生部分还没有被初始化。这意味着在基类构造函数中调用的虚函数将无法正确地访问或使用派生类的成员。此外,派生类中覆盖的虚函数也不会被调用,因为派生类的构造函数尚未执行完毕。
2、析构函数中调用虚函数:
当在基类的析构函数中调用虚函数时,如果正在销毁的对象是一个派生类对象,那么派生类的部分已经被销毁,只剩下基类的部分。此时调用虚函数可能会导致访问已被销毁的派生类成员,从而引发未定义行为。
以下程序是没问题的
class A
{
public:
()
A{
();
func}
virtual ~A()
{
();
func}
virtual void func()
{
<< "A::func" << endl;
cout };
};
class B : public A
{
public:
()
B{
();
func}
~B()
{
();
func}
void func() override
{
<< "B::func" << endl;
cout }
};
int main(int argc, char **argv)
{
;
B b// A::func 此时只有A::func 无B::func
// B::func 此时在执行B构造函数故执行B::func
// B::func 此时在执行B析构函数故执行B::func
// A::func 此时在执行A析构函数只有A::func 无B::func
return 0;
}
像+=、-=、*=操作符函数可以没有返回值,但是如果想有赋值连锁形式就要返回引用
class A
{
public:
()
A{
}
virtual ~A()
{
}
void operator=(const A &a)
{
<< "=" << endl;
cout }
};
int main(int argc, char **argv)
{
;
A a1;
A a2= a2; //=
a1 return 0;
}
赋值连锁形式,如果想要支持这种形式就要返回引用
int x1, x2, x3;
= x2 = x3 = 1;
x1 << " " << x1 << " " << x2 << " " << x3 << endl; // 1 1 1
cout //自定义为
&operator=(const A &a)
A {
<< "=" << endl;
cout return *this;
}
;
Object obj=obj;//这不是有病吗 obj
如何判断与解决此问题呢,或者定义使用std::swap(需要定义swap方法或重写operator=)
class A
{
public:
virtual ~A()
{
}
&operator=(const A &a)
A {
if (this == &a)
{
<< "self" << endl;
cout return *this;
}
<< "other" << endl;
cout //----------------------------------------------------
(a); // 临时副本,一面在复制期间a修改了导致数据不一致
A temp// 赋值操作
//...
//----------------------------------------------------
return *this;
}
};
int main(int argc, char **argv)
{
;
A a= a; // self
a ;
A a1= a1; // other
a return 0;
}
可能一开始的业务是这样,但后来加上了isman属性,但是你却忘了加到拷贝构造和赋值函数中,那么这是异常灾难,可能你还找不出来自己错在哪里
class A
{
public:
() {}
A(const A &a) : num(a.num)
A{
}
&operator=(const A &a)
A {
this->num = a.num;
}
int num;
//bool isman;
};
还有更恐怖的风险,在存在继承时,你可能忘记了基类部分,所以千万不能忘记
class A
{
public:
() {}
Avirtual ~A(){};
(const A &a) : num(a.num)
A{
}
&operator=(const A &a)
A {
this->num = a.num;
return *this;
}
int num;
};
class B : public A
{
public:
() : A()
B{
}
~B() {}
(const B &b) : A(b), priority(b.priority) // 不要忘记
B{
}
&operator=(const B &b)
B {
::operator=(b); // 不要忘记
Athis->priority = b.priority;
return *this;
}
int priority;
};
下面的就是风险较大的情况
void func()
{
int *ptr = new int(5);
// ...
// ... 做许多事情,中间可能会return,措施delete执行
delete ptr;
}
请记住:
#include <iostream>
using namespace std;
class RAII
{
public:
(int *ptr)
RAII{
m_ptr = ptr;
}
~RAII()
{
if (m_ptr)
{
delete m_ptr;
}
}
int *get()
{
return m_ptr;
}
operator int()
{
if (m_ptr)
{
return *m_ptr;
}
return 0;
}
operator int *()
{
return m_ptr;
}
private:
int *m_ptr{nullptr};
};
int main(int argc, char **argv)
{
(new int(9));
RAII ptr*ptr.get() = 323;
std::cout << (*ptr.get()) << std::endl; // 323
int *resource = ptr;
std::cout << *resource << std::endl; // 323
return 0;
}
[]
,必须在相应得delete表达式中也使用[]
。如果你在new表达式中不使用[]
,一定不要在相应delete表达式中使用[]
#include <iostream>
using namespace std;
int main(int argc, char **argv)
{
int *arr = new int[100];
int *ptr = new int;
delete[] arr;
delete ptr;
return 0;
}
这句话什么意思呢?
void function(RAII raii, int i)
{
// do something
}
(RAII(new int(1)), otherFunction(199)); function
这样可能存在内存泄露的风险,因为可能出现下面的情况
万一中间调用otherFunction出现异常就完蛋了,所以请遵守规则,独立创建RAII
(new int(1));
RAII raii(raii, otherFunction(199)); function
设计一个组件,那么设计外部接口是非常重要的,正常使用的情况下,还应该做到不易用错,例如
class Date
{
public:
(int month, int day, int year);
Date};
外部调用Date很容易将三个参数写错,或者写反,造成目的与实际效果不同,很难排查。上面的例子,可以为每类参数设计一个类,如
struct Day
{
explicit Day(int d) : val(d)
{
}
int val;
}
struct Month()
{
static Month Jan() { return Month(1); }
...
};
struct Year()...
class Date
{
public:
(const Month& month, const Day& day, const Year& year);
Date};
例如工厂函数
* createInvestment();
Investmentvoid getRidOfInvestment(Investment);
这样很容易内存泄露,所以要使用智能指针
std::shared_ptr<Investment> createInvestment();
关于使用智能指针则可以为智能指针指定销毁函数,解决跨DLL问题(例如再某个申请的内存指针地址传到另一个DLL使用了另一个DLL的delete,我们尽可能遵循那个DLL申请的内存则由那个DLL的销毁函数进行释放)
std::shared_ptr<Investment> createInvestment()
{
*ptr = new Investment;
Investment std::shared_ptr<Investment> ret(ptr, [](Investment *ptr) -> void
{ delete ptr; });
return ret;
}
请记住
设计新的class应该带着“语言设计者当初设计语言内置类型时”一样的严谨来讨论class的设计。
新type的对象应该如何被创建和销毁?
构造函数和析构函数以及内存分配函数和释放函数operator new
、operator new[]
、operator delete
、operator delete[]
。
对象的初始化和对象的赋值该有什么样的差别?
决定构造函数和赋值操作符的行为。
对象如果被以值传递,意味着什么?
什么是新type的和法值? 构造函数,赋值操作符,setter等。
新类型需要配合某个继承图系吗?
受到函数是virtual或non-virtual的影响,如果允许其他class继承,则会影响所声明的函数尤其是析构函数,是否为virtual。
新类型需要什么样的转换?
写转换函数operator TYPE
或者写non explicit one
argument构造函数等。
什么样的操作符和函数对新类型是合理的?
应该声明哪些函数,操作符成员函数del 自定义功能等等。
什么样的标准函数应该驳回? 声明为private。
谁该取用新类型的成员?
决定合理设计成员的public、protected、private。
什么是新类型读的未声明接口?
新class有多么一般化?
合理使用模板编程。
你真的需要一个新class吗?
根据功能实际情况合理设计。
#include <iostream>
using namespace std;
class A
{
public:
// big content...
};
void dosomething(const A &a)
{
}
int main(int argc, char **argv)
{
;
A a(a);
dosomethingreturn 0;
}
切割问题
#include <iostream>
using namespace std;
class A
{
public:
virtual void run()
{
std::cout << "A::run" << std::endl;
}
};
class B : public A
{
public:
void run() override
{
std::cout << "A::run" << std::endl;
}
};
int main(int argc, char **argv)
{
;
A a// B *b_ptr = dynamic_cast<B *>(&a); // 会报错
*b_ptr = (B *)&a; // 不会报错
B ->run(); // 产生切割问题 输出A::run
b_ptr;
B b_instance= b_instance; // 产生切割问题 只拷贝了基类部分
A a_copy_from_b .run(); // A::run
a_copy_from_breturn 0;
}
如下面的场景返回值类型就挺好的
#include <iostream>
using namespace std;
class Rational;
const Rational operator*(const Rational &lhs, const Rational &rhs);
class Rational
{
public:
(int numberator = 0, int denominator = 1);
Rational
private:
int n, d;
friend const Rational operator*(const Rational &lhs, const Rational &rhs);
};
::Rational(int numberator, int denominator) : n(numberator), d(denominator)
Rational{
}
// 比较好的方式
const Rational operator*(const Rational &lhs, const Rational &rhs)
{
return Rational(lhs.n * rhs.n, lhs.d * rhs.d);
}
int main(int argc, char **argv)
{
return 0;
}
下面返回了local stack 的引用,则会core
#include <iostream>
using namespace std;
class Rational;
const Rational &operator*(const Rational &lhs, const Rational &rhs);
class Rational
{
public:
(int numberator = 0, int denominator = 1);
Rational
private:
int n, d;
friend const Rational &operator*(const Rational &lhs, const Rational &rhs);
};
::Rational(int numberator, int denominator) : n(numberator), d(denominator)
Rational{
}
const Rational &operator*(const Rational &lhs, const Rational &rhs)
{
(lhs.n * rhs.n, lhs.d * rhs.d);
Rational retreturn ret;
}
int main(int argc, char **argv)
{
, r2;
Rational r1= r1 * r2;
Rational r3 return 0;
}
返回local static的引用,也会存在一定问题
#include <iostream>
using namespace std;
class Rational;
const Rational &operator*(const Rational &lhs, const Rational &rhs);
class Rational
{
public:
(int numberator = 0, int denominator = 1);
Rationalbool operator==(const Rational &other) const
{
return this->n == other.n && this->d == other.d;
}
private:
int n,
;
dfriend const Rational &operator*(const Rational &lhs, const Rational &rhs);
};
::Rational(int numberator, int denominator) : n(numberator), d(denominator)
Rational{
}
// 比较好的方式
const Rational &operator*(const Rational &lhs, const Rational &rhs)
{
static Rational ret;
.n = lhs.n * rhs.n;
ret.d = lhs.d * rhs.d;
retreturn ret;
}
int main(int argc, char **argv)
{
(1, 2), r2(3, 4);
Rational r1(5, 6), r4(7, 9);
Rational r3std::cout << boolalpha << ((r1 * r2) == (r3 * r4)) << std::endl; // 总是返回true因为比较的是同一个Rational对象当然总是相等
return 0;
}
假设我们有一个public成员变量,而我们最终取消了它。多少代码可能会被破坏呢?所有使用它的客户代码都会被破坏,而那是一个不可知的大量。因此public成员变量完全没有封装性。
假设我们有一个protected成员变量,而我们最终取消了它,有多少代码被破坏? 所有使用它的derived classes都会被破坏,那往往也是个不可知的大量。
因此,protected成员变量就像public成员变量一样缺乏封装性,
因为在这两种情况下,如果成员变量被改变,都会有不可预知的大量代码受到破坏。 一旦你将一个成员变量声明为public或protected而客户开始使用它,就很难改变那个成员变量所涉及的一切。太多代码需要重写、重新测试、重新编写文档、重新编译。
从封装的角度观之,其实只有两种访问权限:private(提供封装)和其他(不提供封装)。
class WebBrowser
{
public:
// ...
void clearCache();
void clearHistory();
void removeCookies();
void clearEveryhing()
{
();
clearCache();
clearHistory();
removeCookies}
// ...
};
不如写为
namespace WebBrowserStuff
{
class WebBrowser
{
public:
// ...
void clearCache();
void clearHistory();
void removeCookies();
// ...
};
void clearBrowser(WebBrowser &wb)
{
.clearCache();
wb.clearHistory();
wb.removeCookies();
wb}
}
还可以根据功能划分与重要成都写到不同的头文件中
// webbrowser.h
namespace WebBrowserStuff
{
class WebBrowser{...};
// ... 核心机能,如几乎所有客户端需要的non-member函数
}
// webbrowserbookmarks.h
namespace WebBrowserStuff
{
// ... 与书签相关的便利函数
}
// 头文件 webbrowsercookies.h
namspace WebBrowserStuff{
// ... 与cookie相关的便利函数
}
只看描述是很难理解的,看代码的例子,就好很多。
class Rational
{
public:
(int numerator = 0,
Rationalint denominator = 1); // 构造函数刻意不位explicit 允许int-to-Rational隐式转换
int numerator() const;
int denominator() const;
private:
...
};
自定义乘法运算符
class Rational
{
public:
...
const Rational operator* (const Rational& rhs) const;
};
(1, 8);
Rational oneEighth(1, 2);
Rational oneHalf= oneHalf * oneEighth;
Rational result = result * oneEighth;
result = oneHalf.operator*(2);
result = 2.operator*(oneHalf); // 错误
result = operator*(2, oneHalf); // 错误 result
上面的oneHalf.operator*(2)
相当于做了隐式转换
const Rational temp(2);
= oneHalf * temp; // Rational 构造函数是非explicit的 result
想要支持混合式算术运算,让operator*
成为一个non-member函数,允许编译器在每一个实参上执行隐式类型转换:
class Rational
{
... // 不包括operator*
};
// 定义为non-member函数
const Rational operator*(const Rational& lhs, const Rational& rhs)
{
return Rational(lhs.numberator() * rhs.numberator(), lhs.denominator() * rhs.denominator());
}
(1, 4);
Rational oneFourth;
Rational result= oneFourth * 2;
result = 2 * oneFourth; result
std::swap
对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛出异常。std::swap
。std::swap
使用using生命,然后调用swap并且不带任何“命明空间资格修饰”std
内加入某些对std而言全新的东西。所谓swap(置换)两个对象值,意思是将两对象的值彼此赋予对方。缺省情况下swap动作可由标准程序库提供的swap算法完成。
namespace std
{
template <typename T>
void swap(T &a, T &b)
{
(a);
T temp= b;
a = temp;
b }
}
只要类型T支持copying(通过拷贝构造函数和拷贝赋值操作符完成)。
#include <iostream>
#include <vector>
using namespace std;
class AImpl
{
public:
int a, b, c;
std::vector<int> vec;
};
class A
{
public:
()
A{
= new AImpl;
implPtr }
~A()
{
if (implPtr)
{
delete implPtr;
}
}
(const A &a)
A{
= new AImpl(*a.implPtr);
implPtr }
&operator=(const A &a)
A {
// 深拷贝 不然默认只拷贝地址有问题
*implPtr = *a.implPtr;
return *this;
}
*implPtr;
AImpl };
int main(int argc, char **argv)
{
;
A a1= a1;
A a2 return 0;
}
如上面的例子,因为需要拷贝我们必须写深拷贝,不然默认进行地址拷贝会出问题。但是使用std::swap
时就显得有些鸡肋,明明只交换二者的
implPtr存储的地址即可,却使用的拷贝。但是我们对std::swap
针对A进行特化。
namespace std
{
template <>
void swap<A>(A &a, A &b)
{
(a.implPtr, b.implPtr);
swap}
}
上面可以实现,是因为implPtr属性是public的,如果为私有的时应该怎么做
#include <iostream>
#include <vector>
using namespace std;
class A;
class AImpl;
void swap(A &a, A &b) noexcept;
class AImpl
{
public:
int a, b, c;
std::vector<int> vec;
};
class A
{
public:
()
A{
= new AImpl;
implPtr }
~A()
{
if (implPtr)
{
delete implPtr;
}
}
(const A &a)
A{
= new AImpl(*a.implPtr);
implPtr }
&operator=(const A &a)
A {
// 深拷贝 不然默认只拷贝地址有问题
*implPtr = *a.implPtr;
return *this;
}
public:
friend void swap(A &a, A &b) noexcept;
void swap(A &b) noexcept
{
::swap(*this, b);
}
private:
*implPtr;
AImpl };
void swap(A &a, A &b) noexcept
{
std::cout << "my swap" << std::endl;
std::swap(a.implPtr, b.implPtr);
}
namespace std
{
template <>
void swap<A>(A &a, A &b) noexcept
{
::swap(a, b);
}
}
int main(int argc, char **argv)
{
;
A a1= a1;
A a2 std::swap(a1, a2); // my swap
(a1, a2); // my swap
swapreturn 0;
}
如果A是一个模板类是,情况则有些麻烦。在C++中,模板的特例化不能放在std命名空间中,除非标准库特意允许。因为直接在std命名空间中特例化会导致不确定行为。
#include <iostream>
#include <vector>
using namespace std;
namespace ASpace
{
template <typename T>
class A;
class AImpl;
template <typename T>
void swap(A<T> &a, A<T> &b) noexcept;
class AImpl
{
public:
int a, b, c;
std::vector<int> vec;
};
template <typename T>
class A
{
public:
()
A{
= new AImpl;
implPtr }
~A()
{
delete implPtr;
}
(const A &a)
A{
= new AImpl(*a.implPtr);
implPtr }
&operator=(const A &a)
A {
if (this == &a)
{
return *this; // nothing todo
}
// 深拷贝 不然默认只拷贝地址有问题
*implPtr = *a.implPtr;
return *this;
}
public:
friend void swap<>(A<T> &a, A<T> &b) noexcept;
void swap(A &b) noexcept
{
std::swap(implPtr, b.implPtr);
}
private:
*implPtr;
AImpl };
template <typename T>
void swap(A<T> &a, A<T> &b) noexcept
{
std::cout << "my swap" << std::endl;
.swap(b);
a}
} // ASpace
int main(int argc, char **argv)
{
::A<ASpace::AImpl> a1;
ASpace::A<ASpace::AImpl> a2 = a1;
ASpace(a1, a2); // my swap // 触发(argument-dependentlookup或Koenig lookup法则)
swap// std::swap(a1,a2);//error
{
using std::swap;
(a1, a2); // ADL(Argument Dependent Lookup,参数依赖查找) 调用ASpace::swap
swap}
return 0;
}