#C++ Effective # 条款01-03

本文探讨了C++中构造函数的explicit声明及其作用,以及如何通过const语义来增强代码的安全性和理解性。文章还介绍了const关键字在不同上下文中的应用,包括变量、指针、函数参数及返回值。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

0. 导读


class的构造函数被声明为eplicit, 这可以阻止他们被用来执行隐式类型转换,但他们仍可以被用来进行显式类型转换。

除非我有一个好理由允许构造函数被用于隐式类型转换,否则我会把它声明为explicit,他可以禁止编译器执行非预期的类型转换。



= 如何区分是赋值还是拷贝构造呢?

如果有一个新对象被定义,一定会有个构造函数被调用,不可能调用赋值操作。如果没有新对象被定义,就不会有构造函数被调用,就是赋值操作。

A a = b;  //构造

a = b;  //赋值



对一个null指针取值,会导致不明确行为

访问数组的无效索引也会导致不明确行为



1. C++视为一个语言联邦


C 语言是没有模板,没有异常,没有重载的



2. 尽量以const, enum, inline替换#define
这条也可以叫做:宁可以编译器替换预处理器
比如定义一个常量,用宏定义不好追踪,最后用const  name = 1.63 ;这种方式编译器就会看到。

但是还有两个特殊情况:
1. 定义常量指针  由于常量定义式通常在头文件中,因此有必要将指针声明为const。
如:const char* const authorname = "haha"   此时需要写const两次。
不过一般string比char*-based 好,所以这么写:const  std::string authorname("haha");

2. class 专属常量  类的成员变量,为了确保此常量至多只有一份实体,必须成为static
即static const int num = 5;   
但是上面的式子是声明,并非定义,一般c++ 要求你有定义,但是如果是class的static类型的成员变量,而且是int, char, bool类型,只要不取他们的地址,就可以不用定义。但是如果编译器硬要看到定义,你可以这样定义:
const int A::num;  (因为在类中声明的时候获得了初值,所以这里可以不再次设初值)

#define的作用域很大,不能用来定义类的成员变量,也不能提供任何封装性
此外,in-class的初值设定也只允许对整数常量进行

万一你的编译器不允许“static 整数型常量完成in-class初值设定”,可改用所谓的the enum hack 的策略, 即一个枚举型的数值可充当int 被使用
enum {num = 5};
int score[num];  //这是OK的

enum hack的行为比较像#define , 而不像const。
例如取const的地址是合法的,但是取enum得地址不合法,取define的地址通常也不合法
但是如果你不想让别人获得一个pointer或reference指向你的某个整数常量,enum可以帮你实现这个约束。
有些不优秀的编译器会给整数型const对象分配另外的内存,而define 和 enum不会,这样不会导致非必要的内存分配。

enum hack是 template metaprograming的基础技术

#define宏因为是把代码直接拷贝替换,因此最好把所有实参都加上小括号,这样至少会安全一些

利用template inline函数,可以兼顾宏的效率和一般函数的所有可预测行为和类型安全性
template会产生一整群函数

总结:
对于单纯变量,最好以const对象或者enums替换#define
对于形似函数的宏,最好改用inline 函数替换#define


3. 尽可能使用const
语义约束,只要某项的值保持不变是事实,最好指明变量是const,这样会获得编译器的帮助。

const可以在class 外部修饰global, 或namespace作用域中的常量
可以修饰文件、函数、或区块作用域中被声明为static的对象
可修饰class内部的static或non-static成员
对指针,可以指出指针本身,指针所指的东西,或两种都是const
注意区分:   const char* p = greating; // non-const pointer, const data
                  char* const p = greatinh; // const pointer, non-const data
                  const char* const p = greating; //const pointer, const data

 有些程序员会把const 写在类型之前,有的人写在类型之后,星号之前,意义是一样的
const Widget* pm    ==   Widget  const * pm

迭代器的作用类似于T*指针
T* const 表示这个迭代器不得指向不同的东西,但是它所指的东西的值是可以改动的。
如果希望迭代器所指的东西不可被改动,const T*  那么需要的是const iterator

const ::iterator   iter ;
*iter = 10;  //可以改变iter的值
++iter;  //不可以改变iter

const_iterator citer;
*citer = 10; //不可以,是const
++citer; //可以改变citer

const 最有威力的用法是面对函数声明时的应用。
令一个函数返回一个常量值,往往可以降低因客户错误而造成的意外,而又不至于放弃安全和高效性。比如可以避免函数执行完之后 =的赋值操作,虽然有时候是== 打错成了=

令一个成员函数为const,是为了确定该成员函数可以用作const对象身上。这一类函数可以让接口更好理解,哪些可动哪些不可以;他也使操作const对象成为可能。
成员函数如果只是两个常量性不同,可以被重载。

const char& operator[](std::size_t position) const  
{}   // operator for const 对象

char& operator[](std::size_t position)
{}   //operator for non const 对象

如果函数的返回类型是个内置类型,那么改动函数返回值从来就不合法,而且如果是通过值传递,改变了值也只是改变了一个副本,不是改变的本身,也没有作用。

成员函数是const,有两个概念:bitwise constness     logical constness
bitwise: 成员函数只有在不更改对象之任何成员变量时才可以说是const,因此const成员函数不可以更改对象内任何non-static成员变量;但是如果不改指针,只是修改指针所指的东西,那也是可以通过bitwise的,但是却改变了值。

logical:一个const成员函数可以修改他所处理的对象内的某些bits, 但只有在客户端侦测不出得情况下才得如此。
不过此处需要用mutable释放掉non-static成员变量的bitwise constness的约束;
mutable int num;  //那么这些成员变量可以被修改,就算是在const成员函数内

在const和non-const成员函数中避免重复
将常量性转除
比如在类中,有const和non-const函数,除了多一个const之外,没有别的多的,那么此时代码就重复了。可以const的函数保留,对于non-const函数,在函数实现部分将类型进行转换
const_cast<char&>(static_cast<const TextBlock&>(*this)[position]);
两个转型动作
第一次用来为*this 添加const   static_const 安全转型, 第二次从const 函数的返回值中移除const, 利用const_cast完成

这里注意:利用const 版本调用non-const版本以避免重复,并不好。因为const成员函数相当于承诺不改变对象的逻辑状态,non-const函数是没有的。如果用const调用non-const函数,就意味着你违背了承诺。

总结:
将某些东西声明为const可以帮助编译器侦测出错误用法。const可以被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体。

编译器强制实施bitwise constness,但你编写程序时应该使用概念上的常量性;

当const 和non-const成员函数有着实质性等价的实现时,令non-const版本调用const版本可避免代码重复。






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值