effective C++ 读后感(六)若不想使用编译器自动生成的函数,就该明确拒绝

本文详细介绍了如何在C++中通过将析构函数、复制构造函数、复制赋值运算符声明为private来自定义类的行为,以避免编译器自动生成这些函数。并通过定义基类的方式进一步限制类的功能。

六、若不想使用编译器自动生成的函数,就该明确拒绝

前一节讲过,如果代码中没有声明,编译器会自动生成一个析构函数,copy构造函数,copy assignment操作符和一个无参数的构造函数,而有些时候,我们并不希望某个类具有全部(或部分)这些功能。如对于一些独一无二的对象(如房子),我们不希望它能被赋值或拷贝,该怎么办呢?这似乎出现了矛盾,一方面,如果不想让编译器生成这些函数,需 要在代码中将这些函数显示声明出来,而我们恰恰不希望类有这样的功能!其实并不是这样,在前一节里,我们也可以看出来,问题关键在于编译器自动生成的这些函数都是public的,如果我们在代码中将其声明为private的,问题基本上就解决了。


为什么说是“基本上”解决呢?不要忘了还有友元函数和成员函数还是可以调用它们的。所以,真正的解决方法有两个:

一、将这些方法声明为private,而且不实现它。那么当成员函数或友元函数调用这些方法时,虽然在编译时期查不出错误,但在链接时期会出错。

二、如果希望都在编译时期就能查出错误(毕竟越早发现错误越好),则可以定义一个基类,将这此方法声明为private的,让那些不想编译器生成这些方法的类都继承自这个类。如,我们希望一个类A不能被复制(即不希望编译器自动生成copy构造函数和 copy assignment操作符,可以定义一个Uncopyable类如下:

#include <iostream>
using namespace std;

class Uncopyable {
public:
	Uncopyable(){}
	~Uncopyable(){}
private:
	Uncopyable(const Uncopyable &);
	Uncopyable& operator=(const Uncopyable &);
};

class A: public Uncopyable {

};

这样,就算是A的成员函数和友元函数都不能复制A的对象了。


在Boost中的noncopyable就是这样实现的:

#ifndef BOOST_NONCOPYABLE_HPP_INCLUDED
#define BOOST_NONCOPYABLE_HPP_INCLUDED

namespace boost {

//  Private copy constructor and copy assignment ensure classes derived from
//  class noncopyable cannot be copied.

//  Contributed by Dave Abrahams

namespace noncopyable_  // protection from unintended ADL
{
  class noncopyable
  {
   protected:
      noncopyable() {}
      ~noncopyable() {}
   private:  // emphasize the following members are private
      noncopyable( const noncopyable& );
      const noncopyable& operator=( const noncopyable& );
  };
}

typedef noncopyable_::noncopyable noncopyable;

} // namespace boost

#endif  // BOOST_NONCOPYABLE_HPP_INCLUDED


独立储能的现货电能量与调频辅助服务市场出清协调机制(Matlab代码实现)内容概要:本文围绕“独立储能的现货电能量与调频辅助服务市场出清协调机制”展开,提出了一种基于Matlab代码实现的优化模型,旨在协调独立储能系统在电力现货市场与调频辅助服务市场中的联合出清问题。文中结合鲁棒优化、大M法和C&CG算法处理不确定性因素,构建了多市场耦合的双层或两阶段优化框架,实现了储能资源在能量市场和辅助服务市场间的最优分配。研究涵盖了市场出清机制设计、储能运行策略建模、不确定性建模及求解算法实现,并通过Matlab仿真验证了所提方法的有效性和经济性。; 适合人群:具备一定电力系统基础知识和Matlab编程能力的研究生、科研人员及从事电力市场、储能调度相关工作的工程技术人员。; 使用场景及目标:①用于研究独立储能在多电力市场环境下的协同优化运行机制;②支撑电力市场机制设计、储能参与市场的竞价策略分析及政策仿真;③为学术论文复现、课题研究和技术开发提供可运行的代码参考。; 阅读建议:建议读者结合文档中提供的Matlab代码与算法原理同步学习,重点关注模型构建逻辑、不确定性处理方式及C&CG算法的具体实现步骤,宜在掌握基础优化理论的前提下进行深入研读与仿真调试。
<think> 我们正在讨论C++编译器自动合成构造函数的情况以及表述的准确性。 根据C++标准,在某些情况下,如果程序员没有为一个类显式定义构造函数编译器会隐式声明一个默认构造函数。这个过程通常被称为“自动合成”或“隐式生成”。 引用[1]提到函数原型(声明)和函数定义的区别,这里不直接相关,但提醒我们注意声明和定义的区别:编译器隐式声明构造函数,并在需要时定义(生成)它。 引用[2]中关于函数原型的讨论,可以帮助我们理解编译器为什么需要知道函数的类型信息。同样,对于构造函数编译器需要知道如何构造对象。 引用[3]是关于类型推导的,可以间接关联,因为编译器在生成构造函数时涉及类型处理。 引用[4]强调按照ISO标准C++,因此我们将依据C++标准来回答。 重点:根据C++标准,编译器在以下情况下会合成默认构造函数(注意:是“合成”还是“隐式声明并定义”?通常,我们使用“隐式生成”或“合成”都是可以接受的,但要注意其含义)。 经典著作《Effective C++》和《Inside the C++ Object Model》对此有详细描述。 情况: 1. 当类成员中含有带有默认构造函数的类对象,并且该对象所属的类没有用户定义的构造函数,那么编译器会生成一个默认构造函数,以调用该成员的默认构造函数。 2. 基类带有默认构造函数的派生类:如果一个派生类的基类有默认构造函数(可以是用户定义或编译器合成),而派生类没有定义任何构造函数,那么编译器会为该派生类合成一个默认构造函数,以调用基类的默认构造函数。 3. 类中声明了虚函数(需要初始化虚函数表指针)。 4. 虚继承(需要初始化虚基类指针或偏移量)。 但是,注意:C++11以后,规则有所变化,例如如果类中有成员变量在声明时进行了初始化(类内初始化),那么编译器也会为它生成默认构造函数。 另外,如果用户定义了任何构造函数(包括拷贝构造函数、移动构造函数等),编译器就不会再生成默认的无参构造函数,除非显式要求(使用=default)。 关于表述的准确性: “自动合成构造函数”是一种常见的表述,在Scott Meyers的书籍和许多文献中都有使用。但是,严格来说,按照C++标准,编译器隐式声明并定义默认构造函数。术语“合成”更偏向于实现层面,而标准中描述为“隐式声明”和“隐式定义”。 因此,我们可以说: - 在用户没有显式声明构造函数的情况下,编译器会隐式声明一个默认构造函数。 - 如果这个隐式声明的默认构造函数被调用(即需要创建对象),编译器会隐式定义它(即生成函数体)。这个定义的过程就是“合成”。 所以,表述“编译器自动合成构造函数”是准确的,但要注意语境。在学术讨论中,我们通常使用标准术语“隐式声明”和“隐式定义”。 总结: 编译器在以下情况下会隐式生成(合成)默认构造函数: 1. 类中含有带有默认构造函数的成员对象(且该成员类没有用户定义的构造函数?注意:只要成员类有可访问的默认构造函数,无论它是用户定义还是编译器生成,都会导致合成)。 2. 基类有默认构造函数(同样,无论基类的默认构造函数是用户定义还是编译器生成)。 3. 类含有虚函数(需要初始化虚表指针)。 4. 类虚继承其他类(需要处理虚基类)。 5. (C++11起)类中有非静态数据成员使用类内初始化器,且没有用户提供的构造函数。 注意:如果用户声明了任何构造函数(包括拷贝构造函数、移动构造函数等),编译器就不会再隐式生成默认构造函数。但用户可以使用`=default`要求编译器生成。 另外,合成的默认构造函数只会执行必要的操作(如调用基类或成员的构造函数,初始化虚表指针等),而不会初始化内置类型的非静态数据成员(除非有类内初始化器)。 因此,对于内置类型成员,如果没有类内初始化,则其值将是未定义的。 示例代码: 以下情况编译器会合成默认构造函数: ```cpp class Base {}; class Member { public: Member() {} // 用户定义的默认构造函数 }; class MyClass : public Base { // 基类Base有编译器合成的默认构造函数(隐式生成的) Member m; // 成员对象m有默认构造函数 int a; // 内置类型,没有初始化(在合成的默认构造函数中不会初始化) }; // 使用 MyClass obj; // 这里,编译器会为MyClass合成一个默认构造函数,用于调用Base的默认构造函数和Member的默认构造函数,但a是未初始化的。 ``` 引用标准:C++标准文档(如ISO/IEC 14882)的相关章节(如C++11标准的12.1节)。 注意:在C++11之后,如果类中有成员变量使用了类内初始化,那么即使没有用户定义的构造函数编译器也会合成默认构造函数来初始化这些成员(按照类内初始化器)。 例如: ```cpp class WithInit { int x = 10; // 类内初始化 }; // 编译器会合成默认构造函数,将x初始化为10。 ``` 因此,“自动合成构造函数”的表述是准确的,但需要理解其背后的规则。 相关问题: 1. 什么情况下编译器会自动合成拷贝构造函数? 2. 合成的默认构造函数会初始化哪些成员?不会初始化哪些成员? 3. C++11中类内初始化对合成构造函数有什么影响? 4. 如何阻止编译器自动合成构造函数? 5. 使用“=default”和编译器自动合成构造函数有什么区别? </think> ### C++编译器自动合成构造函数的情况及表述准确性分析 #### 1. **自动合成构造函数的情况** 根据C++标准(ISO/IEC 14882),编译器在以下场景会自动合成(隐式生成)默认构造函数: ##### (1) **类成员包含默认构造对象** 当类包含**具有默认构造函数的成员对象**,且该类没有声明任何构造函数时: ```cpp class Member { public: Member() {} // 用户定义的默认构造函数 }; class MyClass { Member m; // 触发MyClass默认构造函数的合成 int x; }; // 编译器合成 MyClass::MyClass() ``` ##### (2) **基类包含默认构造函数** 当派生类的**基类有默认构造函数**,且派生类未声明任何构造函数时: ```cpp class Base { public: Base() {} }; class Derived : public Base { // 编译器合成 Derived::Derived() 以调用 Base() }; ``` ##### (3) **虚函数或虚继承** 类声明**虚函数**或**虚继承**时: ```cpp class VirtualBase {}; class MyClass : virtual public VirtualBase { // 虚继承 virtual void foo() {} // 虚函数 }; // 合成默认构造函数初始化虚表指针 ``` ##### (4) **C++11 类内初始化** 类中有成员使用**类内初始化器**(C++11 起): ```cpp class WithInit { int x = 10; // 类内初始化 }; // 合成默认构造函数用于初始化 x ``` #### 2. **"自动合成"表述的准确性** - **表述合理但需注意细节**: - ✅ **标准术语**:C++标准称此为 *implicitly declared*(隐式声明)和 *implicitly defined*(隐式定义),"合成"是通用技术表述(见《Inside the C++ Object Model》)[^1]。 - ⚠️ **条件限制**:合成仅在**未被用户覆盖**时发生(若用户定义任意构造函数,则不会合成默认构造函数)。 - ❌ **不完整初始化**:合成的构造函数**不会初始化内置类型成员**(如 `int`),除非有类内初始化器: ```cpp class Problem { int y; // 未初始化! }; Problem p; // p.y 值随机 ``` #### 3. **验证编译器行为的方法** 可参考引用[^3]的方法获取合成构造函数的类型信息: ```cpp template<typename T> class TD; // 类型探测器 class Test {}; Test t; TD<decltype(t)> t_type; // 编译错误信息显示实际类型 ``` 编译错误将显示合成的构造函数签名(如 `Test::Test()`)。 #### 4. **标准合规性建议** 遵循引用[^4]的准则: > 优先使用 ISO 标准 C++,必要时封装平台相关扩展。编译器合成行为是标准核心机制,但需明确其边界条件: > - 内置类型成员需手动初始化 > - 虚函数处理依赖编译器实现 > - 移动构造函数的合成规则(C++11) --- ### 相关问题 1. **合成构造函数与用户自定义构造函数共存时会发生什么?** [提示:用户定义构造函数会阻止默认构造函数的合成] 2. **如何显式阻止编译器合成构造函数?** [提示:使用 `= delete` 或声明为 `private`] 3. **C++11 后合成构造函数的行为有哪些关键变化?** [提示:类内初始化、默认成员初始化] 4. **合成构造函数对 POD(Plain Old Data)类型有何影响?** [提示:POD 类型是否触发合成?] 5. **虚函数表(vtable)如何在合成的构造函数中初始化?** [提示:不同编译器的实现差异]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值