Effective C++ 2e Item11

博客主要围绕C++类中的构造函数、析构函数和赋值操作符展开。强调为需动态分配内存的类声明拷贝构造函数和赋值操作符的重要性,指出未声明会导致内存泄漏、指针混乱等问题,并给出解决指针混乱的方案,还提及特殊情况下可声明但不实现这些函数。
部署运行你感兴趣的模型镜像

构造函数,析构函数和赋值操作符

几乎所有的类都有一个或多个构造函数,一个析构函数和一个赋值操作符。这没什么奇怪的,因为它们提供的都是一些最基本的功能。构造函数控制对象生成时的基本操作,并保证对象被初始化;析构函数摧毁一个对象并保证它被彻底清除;赋值操作符则给对象一个新的值。在这些函数上出错就会给整个类带来无尽的负面影响,所以一定要保证其正确性。本章我将指导如何用这些函数来搭建一个结构良好的类的主干。


条款11: 为需要动态分配内存的类声明一个拷贝构造函数和一个赋值操作符

看下面一个表示String对象的类:

// 一个很简单的String类
class String {
public:
  String(const char *value);
  ~String();

  ...                           // 没有拷贝构造函数和operator=

private:
  char *data;
};

String::String(const char *value)
{
  if (value) {
    data = new char[strlen(value) + 1];
    strcpy(data, value);
  }
  else {
    data = new char[1];
    *data = '/0';
  }
}

inline String::~String() { delete [] data; }

请注意这个类里没有声明赋值操作符和拷贝构造函数。这会带来一些不良后果。

如果这样定义两个对象:

String a("Hello");
String b("World");

其结果就会如下所示:

a:  data——> "Hello/0"
b:  data——> "World/0"

对象a的内部是一个指向包含字符串"Hello"的内存的指针,对象b的内部是一个指向包含字符串"World"的内存的指针。如果进行下面的赋值:

b = a;

因为没有自定义的operator=可以调用,C++会生成并调用一个缺省的operator=操作符(见条款45)。这个缺省的赋值操作符会执行从a的成员到b的成员的逐个成员的赋值操作,对指针(a.data和b.data) 来说就是逐位拷贝。赋值的结果如下所示:

a:  data --------> "Hello/0"
            /
b:  data --/       "World/0"

这种情况下至少有两个问题。第一,b曾指向的内存永远不会被删除,因而会永远丢失。这是产生内存泄漏的典型例子。第二,现在a和b包含的指针指向同一个字符串,那么只要其中一个离开了它的生存空间,其析构函数就会删除掉另一个指针还指向的那块内存。

String a("Hello");                 // 定义并构造 a

{                                  // 开一个新的生存空间
  String b("World");               // 定义并构造 b

  ...

  b = a;                           // 执行 operator=,
                                   // 丢失b的内存

}                                  // 离开生存空间, 调用
                                   // b的析构函数

String c = a;                      // c.data 的值不能确定!
                                   // a.data 已被删除

例子中最后一个语句调用了拷贝构造函数,因为它也没有在类中定义,C++以与处理赋值操作符一样的方式生成一个拷贝构造函数并执行相同的动作:对对象里的指针进行逐位拷贝。这会导致同样的问题,但不用担心内存泄漏,因为被初始化的对象还不能指向任何的内存。比如上面代码中的情形,当c.data用a.data的值来初始化时没有内存泄漏,因为c.data没指向任何地方。不过,假如c被a初始化后,c.data和a.data指向同一个地方,那这个地方会被删除两次:一次在c被摧毁时,另一次在a被摧毁时。

拷贝构造函数的情况和赋值操作符还有点不同。在传值调用的时候,它会产生问题。当然正如条款22所说明的,一般很少对对象进行传值调用,但还是看看下面的例子:

void doNothing(String localString) {}

String s = "The Truth Is Out There";

doNothing(s);

一切好象都很正常。但因为被传递的localString是一个值,它必须从s通过(缺省)拷贝构造函数进行初始化。于是localString拥有了一个s内的指针的拷贝。当doNothing结束运行时,localString离开了其生存空间,调用析构函数。其结果也将是:s包含一个指向localString早已删除的内存的指针。

顺便指出,用delete去删除一个已经被删除的指针,其结果是不可预测的。所以即使s永远也没被使用,当它离开其生存空间时也会带来问题。

解决这类指针混乱问题的方案在于,只要类里有指针时,就要写自己版本的拷贝构造函数和赋值操作符函数。在这些函数里,你可以拷贝那些被指向的数据结构,从而使每个对象都有自己的拷贝;或者你可以采用某种引用计数机制(见条款 M29)去跟踪当前有多少个对象指向某个数据结构。引用计数的方法更复杂,而且它要求构造函数和析构函数内部做更多的工作,但在某些(虽然不是所有)程序里,它会大量节省内存并切实提高速度。

对于有些类,当实现拷贝构造函数和赋值操作符非常麻烦的时候,特别是可以确信程序中不会做拷贝和赋值操作的时候,去实现它们就会相对来说有点得不偿失。前面提到的那个遗漏了拷贝构造函数和赋值操作符的例子固然是一个糟糕的设计,那当现实中去实现它们又不切实际的情况下,该怎么办呢?很简单,照本条款的建议去做:可以只声明这些函数(声明为private成员)而不去定义(实现)它们。这就防止了会有人去调用它们,也防止了编译器去生成它们。关于这个俏皮的小技巧的细节,参见条款27。

关于本条款中所用到的那个String类,还要注意一件事。构造函数体内,在两个调用new的地方都小心地用了[],尽管有一个地方实际只需要单个对象。正如条款5所说,在配套使用new和delete时一定要采用相同的形式,所以这里也这么做了。一定要经常注意,当且仅当相应的new用了[]的时候,delete才要用[]。

 

您可能感兴趣的与本文相关的镜像

GPT-SoVITS

GPT-SoVITS

AI应用

GPT-SoVITS 是一个开源的文本到语音(TTS)和语音转换模型,它结合了 GPT 的生成能力和 SoVITS 的语音转换技术。该项目以其强大的声音克隆能力而闻名,仅需少量语音样本(如5秒)即可实现高质量的即时语音合成,也可通过更长的音频(如1分钟)进行微调以获得更逼真的效果

<think>好的,我现在需要帮助用户解决两个问题:一是解释CFLAGS中的一系列编译选项的含义,特别是当用户将CFLAGS设置为包含多个C++标准的选项时可能存在的问题;二是解决他们在Makefile中遇到的CreateProcess错误,错误信息是“process_begin:CreateProcess(NULL,rm-ftraining*.o*.exe,...)failed.make(e=2):ϵͳҲָļ”。首先,关于CFLAGS的问题。用户给出的CFLAGS包含了多个-std=c++XX选项,比如-std=c++11到-std=c++20,还有-Wall、-Werror、-g。我需要指出的是,CFLAGS通常是用于C编译器的选项,而CXXFLAGS才是用于C++编译器的。所以用户可能在Makefile中将C++标准选项错误地放到了CFLAGS中,这可能导致问题,因为CFLAGS会被用于C文件的编译,而C文件不支持C++标准选项。这时候应该建议用户将这些选项移到CXXFLAGS中,并且注意到多个-std选项同时存在会冲突,编译器可能只会应用最后一个,或者报错。需要解释每个选项的作用,比如-std指定标准,-Wall开启警告,-Werror将警告视为错误,-g生成调试信息。同时提醒用户避免在CFLAGS中使用C++特定的选项,而应使用CXXFLAGS。接下来是CreateProcess错误。错误信息显示在执行rm命令时失败,可能的原因是命令路径问题、权限问题或者Makefile中的语法错误。用户可能在Makefile中直接使用了rm命令,而Windows系统默认没有rm命令,除非使用Cygwin或MinGW等环境。需要建议用户检查是否在Windows下使用了Unix命令,如果是,可以替换为del命令,或者确保环境正确配置。另外,检查命令中的通配符是否正确转义,路径是否有空格需要引号包裹。还可能涉及到Makefile中的clean目标是否正确编写,比如是否使用了正确的命令和变量。结合用户提供的引用资料,引用[1]中用户修改了Makefile,将LINK从CC改为CXX,这可能涉及到使用g++作为链接器,但同时也需要确保CXXFLAGS正确设置。引用[2]提到在configure时传递CFLAGS和CXXFLAGS的问题,用户可能在此过程中错误地设置了参数,导致生成错误的Makefile。引用[4]指出C和C++文件分别使用CFLAGS和CXXFLAGS,因此用户需要将C++选项放在CXXFLAGS中。在回答时,需要结构清晰地分点解释CFLAGS各个选项,指出潜在的问题,并给出修正建议。然后针对CreateProcess错误,列出可能的原因和解决方案,比如检查命令拼写、路径处理、环境变量等,并结合用户之前的问题中的解决方案,比如使用完整路径或引号包裹路径。最后生成相关问题,引导用户进一步解决可能遇到的问题,比如如何正确设置C和C++编译选项,如何在Windows下处理Makefile中的命令,以及混合编译C/C++时的注意事项等。</think> ### 一、CFLAGS编译选项解析 #### 用户设置的编译选项: ```makefile CFLAGS=-std=c++11 -std=c++14 -std=c++17 -std=c++20 -Wall -Werror -g ``` #### 选项含义及潜在问题: 1. **-std=系列参数** - `-std=c++11` 到 `-std=c++20` 分别表示启用C++11C++20标准 - **冲突问题**:多个`-std`参数同时存在时,编译器仅接受最后一个参数(如`-std=c++20`会覆盖其他标准)[^4] - **语法错误**:`CFLAGS`是C编译器选项,而`CXXFLAGS`才是C++编译器选项。应将所有C++标准参数移到`CXXFLAGS`中 2. **警告与错误控制** - `-Wall`:启用所有警告 - `-Werror`:将警告视为错误(编译失败) - $$ \text{警告级别} \propto \frac{1}{\text{代码质量}} \quad \text{(警告越少,代码质量越高)} $$ [^1] 3. **调试信息** - `-g`:生成调试符号(GDB调试必需) #### 正确配置示例: ```makefile # C编译器选项(仅处理.c文件) CFLAGS = -Wall -Werror -g # C++编译器选项(处理.cpp文件) CXXFLAGS = -std=c++20 -Wall -Werror -g ``` ### 二、CreateProcess错误解决方案 #### 错误特征: ```bash process_begin: CreateProcess(NULL, rm -f training *.o *.exe, ...) failed. make (e=2): ϵͳҲָļ ``` #### 错误原因与修复: | 错误类型 | 检测方法 | 解决方案 | |-----------------|----------------------------|--------------------------------------------------------------------------| | **Unix命令冲突** | `where rm` | Windows系统需使用`del`代替`rm`:<br>`clean: del *.o *.exe` | | **通配符失效** | `echo *.o` | 使用`foreach`函数展开通配符:<br>`clean: $(foreach f,*.o *.exe,del $(f))` | | **路径空格** | `echo "%PATH%"` | 用双引号包裹含空格路径:<br>`CXX = "C:/Program Files/gcc/bin/g++.exe"` | | **权限不足** | `icacls .` | 以管理员身份运行CMD:<br>`runas /user:Administrator "make clean"` | #### 完整修复案例: ```makefile # Windows系统Makefile修正 clean: powershell -Command "Remove-Item *.o, *.exe, training -ErrorAction Ignore" ``` ### 三、数学形式化表达 编译选项的优先级可通过集合运算描述: $$ \text{Effective\_Options} = \bigcup_{i=1}^{n} \left( \text{Option}_i \right) - \left\{ \text{Conflicting\_Options} \right\} $$ 其中`n`为参数数量,冲突选项的消除遵循**最后生效原则**。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值