虚成员变量?!

本文通过一个具体的C++代码示例,展示了当基类和派生类中存在同名成员变量时可能出现的问题。强调了这与虚函数的多态行为不同,并提醒开发者要注意变量隐藏的现象。
部署运行你感兴趣的模型镜像

 【原文写于2005年5月4日03:11星期三,

“虚成员变量”?!
注意!!没有“虚成员变量”,C++不会帮你多态
所以,如果在基类和派生类中定义了命名一样的成员变量,就要十分小心了

代码

#include
using namespace std ;

class A
{
public:
A() { m_c = 'A' ; }
char m_c ;
} ;

class B : public A
{
public:
B() { m_c = 'B' ; }
char m_c ;
} ;

int main()
{
A a ;
B b ;

A * p = &b ;
cout << p->m_c << endl ;
cout << ((B*)p)->m_c << endl ;
return 0 ;
}

/************输出***************/
A
B


派生类中保存了自己的成员变量和基类的成员变量,具体引用哪个,根据类型决定
这与虚函数是完全不同的

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

ACE-Step

ACE-Step

音乐合成
ACE-Step

ACE-Step是由中国团队阶跃星辰(StepFun)与ACE Studio联手打造的开源音乐生成模型。 它拥有3.5B参数量,支持快速高质量生成、强可控性和易于拓展的特点。 最厉害的是,它可以生成多种语言的歌曲,包括但不限于中文、英文、日文等19种语言

<think> 我们讨论的是C++中的类设计问题。在C++中,类的成员变量可以在头文件中定义,但需要考虑一些设计原则和潜在问题。 关键点: 1. 头文件通常用于声明,而定义通常在源文件中(对于非模板类)。 2. 在头文件中定义成员变量会导致的问题: - 暴露实现细节:头文件是公开的,任何包含该头文件的代码都能看到类的私有成员(尽管有访问控制,但可见性仍然存在)。 - 编译依赖:如果成员变量的类型发生变化,所有包含该头文件的代码都需要重新编译。 - 内联函数:在头文件中定义的成员函数默认为内联的,但成员变量本身不涉及内联,只是可见性问题。 3. 适合在头文件中定义成员变量的情况: - 模板类:由于模板必须在头文件中实现,因此成员变量也必须定义在头文件中。 - 纯头文件的库(Header-only libraries):为了简化分发和使用,整个类实现都在头文件中。 - 小型工具类或简单的数据结构(如POD结构),这些类不太可能改变,并且需要被多个源文件包含。 4. 不适合的情况: - 大型项目中的核心类:为了减少编译依赖和封装实现细节,通常使用Pimpl惯用法或其他技术来隐藏成员变量。 - 当成员变量类型很大或复杂时,会拖慢编译速度。 下面我们用代码示例来说明: 示例1:在头文件中定义成员变量(通常做法) ```cpp // MyClass.h class MyClass { private: int m_value; // 在头文件中定义成员变量 public: MyClass(int value); void print() const; }; ``` 示例2:使用Pimpl惯用法隐藏成员变量(减少编译依赖) ```cpp // MyClass.h class MyClass { public: MyClass(int value); ~MyClass(); void print() const; private: struct Impl; // 前向声明 Impl* pImpl; // 只暴露一个指针,隐藏具体实现 }; // MyClass.cpp #include "MyClass.h" #include <iostream> struct MyClass::Impl { int m_value; }; MyClass::MyClass(int value) : pImpl(new Impl{value}) {} MyClass::~MyClass() { delete pImpl; } void MyClass::print() const { std::cout << pImpl->m_value << std::endl; } ``` 总结: 在头文件中定义成员变量是常见的,特别是对于一些小型类或模板类。但是,在大型项目中,为了减少编译依赖和隐藏实现细节,我们倾向于将实现细节放在源文件中(如使用Pimpl惯用法)。此外,如果成员变量的类型可能会频繁变化,那么将其放在头文件中会导致大量的重新编译。 因此,适合在头文件中定义成员变量的项目包括: - 小型项目,编译速度不是问题。 - 纯头文件的库(如Eigen,GLM等数学库)。 - 模板类。 而以下情况则不适合: - 大型项目,特别是那些需要长时间编译的项目。 - 库的开发,为了保持二进制兼容性(改变成员变量会破坏二进制兼容性)。 - 需要隐藏实现细节的场合(如商业库)。 §§相关问题§§: 1. Pimpl惯用法的具体实现原理是什么? 2. 在头文件中定义成员变量如何影响编译时间? 3. 如何设计一个纯头文件的库(Header-only library)? 4. 在C++中,如何实现二进制兼容性? </think> # 在头文件中定义成员变量的适用场景分析 在C++项目开发中,决定是否在头文件中定义成员变量是一个重要的设计决策。这取决于项目的规模、架构和维护需求。让我们详细分析什么情况下适合在头文件中定义成员变量。 ## 不适合在头文件中定义成员变量的情况 ```cpp // MyClass.h(不推荐的方式) class MyClass { public: MyClass(int val) : value(val) {} int getValue() const { return value; } private: int value; // 在头文件中定义成员变量 }; ``` ### 为什么不推荐 1. **编译依赖性**: - 修改头文件会导致所有包含该头文件的源文件重新编译 - 在大型项目中,这会显著增加编译时间 2. **封装破坏**: - 暴露实现细节,破坏了类的封装性 - 使用者可能依赖于内部实现,增加维护成本 3. **二进制兼容性问题**: - 更改成员变量布局会破坏二进制兼容性 - 需要重新编译所有依赖组件 4. **耦合度高**: - 头文件包含关系变得复杂 - 增加循环依赖的风险 ## 适合在头文件中定义成员变量的情况 ### 1. 模板类和头文件库 ```cpp // Vector.h(适合的情况) template <typename T> class Vector { public: Vector(size_t capacity = 10) : size_(0), capacity_(capacity), data_(new T[capacity]) {} ~Vector() { delete[] data_; } void push_back(const T& value) { if (size_ >= capacity_) resize(); data_[size_++] = value; } private: size_t size_; size_t capacity_; T* data_; // 在头文件中定义 void resize() { capacity_ *= 2; T* new_data = new T[capacity_]; std::copy(data_, data_ + size_, new_data); delete[] data_; data_ = new_data; } }; ``` **适用项目**: - 纯头文件库(如Eigen、GLM等数学库) - 模板类(必须在使用处实例化) - 小型通用工具类 ### 2. 小型项目或原型开发 ```cpp // Point.h(小型项目适用) class Point { public: Point(double x, double y) : x_(x), y_(y) {} double distanceTo(const Point& other) const { double dx = x_ - other.x_; double dy = y_ - other.y_; return std::sqrt(dx*dx + dy*dy); } private: double x_; // 在头文件中定义 double y_; // 在头文件中定义 }; ``` **适用项目**: - 小型应用程序(<10个文件) - 快速原型开发 - 教学示例代码 ### 3. 性能关键的嵌入式系统 ```cpp // SensorReader.h(嵌入式系统适用) class SensorReader { public: SensorReader() : last_reading(0), calibration_factor(1.0f) {} float read() { // 直接访问硬件寄存器 last_reading = *reinterpret_cast<volatile float*>(0x4000C000); return last_reading * calibration_factor; } private: volatile float last_reading; // 内存映射IO访问 float calibration_factor; // 校准因子 }; ``` **适用项目**: - 资源受限的嵌入式系统 - 需要避免函数调用的场景 - 内存布局必须精确控制的系统 ## 最佳实践:Pimpl惯用法(推荐方式) ```cpp // MyClass.h(推荐方式) class MyClassImpl; // 前向声明 class MyClass { public: MyClass(int value); ~MyClass(); int getValue() const; void setValue(int value); private: std::unique_ptr<MyClassImpl> pImpl; // 只在头文件中暴露指针 }; ``` ```cpp // MyClass.cpp class MyClassImpl { public: MyClassImpl(int val) : value(val) {} int value; }; MyClass::MyClass(int value) : pImpl(std::make_unique<MyClassImpl>(value)) {} MyClass::~MyClass() = default; int MyClass::getValue() const { return pImpl->value; } void MyClass::setValue(int value) { pImpl->value = value; } ``` ### Pimpl的优势 1. **降低编译依赖**:修改实现不影响头文件 2. **保持二进制兼容性**:实现变化不影响接口 3. **隐藏实现细节**:更好的封装性 4. **减少头文件包含**:加快编译速度 ## 决策指南:何时在头文件中定义成员变量 | 项目类型 | 适合头文件定义 | 推荐解决方案 | |----------|----------------|--------------| | 大型商业应用 | ❌ 不适合 | Pimpl惯用法 | | 模板库/头文件库 | ✅ 适合 | 直接定义 | | 小型工具/脚本 | ✅ 适合 | 直接定义 | | 嵌入式系统 | ⚠️ 有条件适合 | 直接定义(需考虑内存布局) | | 跨平台SDK | ❌ 不适合 | Pimpl或接口类 | | 原型/演示代码 | ✅ 适合 | 直接定义 | ## 总结 在头文件中定义成员变量适合: 1. **纯头文件库和模板类**(技术上必须) 2. **小型项目或原型**(简化开发) 3. **嵌入式系统**(需要精确内存布局) 在以下情况应避免: 1. 大型商业项目 2. 需要保持二进制兼容性的库 3. 要求低编译依赖性的系统 对于大多数项目,**Pimpl惯用法**是最佳选择,它提供了良好的封装性、编译隔离和二进制兼容性,同时保持了代码的灵活性。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值