类的成员变量应该使用引用类型、指针类型还是对应类型对象呢?

一、问题

如以下代码,当class A中有数据成员是class B时,我们在A中定义成员变量的时候,是应该使用B的指针类型、引用类型还是直接使用类型B呢?

class B{
  public:
    int _int_elem;;
    double _double_elem;
};

// 方式一
// 利用指针或者智能指针定义class A中数据成员B
class A
{
  public:
    B* _b;
};

// 方式二
// 利用引用定义class A中数据成员B
class A
{
  public:
    // 引用成员变量必须使用构造函数初始化列表进行初始化
    A(B& b):_b(b){}
    B& _b;
};

// 方式三
// class A中数据成员B使用数据实体定义
class A
{
  public:
    B b;
};

二、结论

成员变量使用引用、指针还是直接使用对象是视情况而定。

  1. 因为引用类型必须在初始化列表初始化,并且不能被重新赋值,使用较少。
  2. 指针类型适合在以下几种情况下使用:
    1. 当成员变量要使用多态时,将成员变量定义成指针。
    2. 当成员变量是可选的,在有些情况下不会使用,可以将成员变量定义成指针,节省空间。
    3. 当成员变量是一个很大的对象时,将成员定义成指针,避免栈溢出,linux默认栈大小可以通过ulimit -s查看,默认为8Mb。
    4. 当成员变量对应的资源不是对象独有,而是多个对象共同所有。
  3. 其他情况直接定义指定类型对象较好,无需进行内存管理,无需担心空指针。

三、各自优缺点

3.1 使用指针类型数据成员

3.1.1 优点

  1. 类对象在内存中占用的会更小。需要注意,只是类对象本身占用的空间变小了,不等同于可以节省内存空间,要是指针指向的空间是每个类对象独有的,是不会节省内存空间的,要是指针指向的空间是多个类对象共同所有的,则可以起到节省空间的作用。
  2. 只需要指针指向类的前置声明,不需要引入对应类的头文件,减少依赖,加快编译速度。
  3. 对应成员可以使用多态。
  4. 懒初始化,当成员变量是可选的,在有些情况下不会使用时,可以避免分配空间。

3.1.2 缺点

  1. 需要进行内存管理。通常需要自己实现析构函数,拷贝构造函数和赋值运算符,保证对象被正确销毁,拷贝,赋值,例如如下示例。
#include <iostream>
#include <memory>
class Wheel{
public:
    Wheel(double radius):_radius(radius){}
    double _radius;
};

class Car{
public:
    Car(double wheel_radius){
        _wheel_ptr = std::make_shared<Wheel>(wheel_radius);
    }
    std::shared_ptr<Wheel> _wheel_ptr;
};

int main(){
    Car car1(10);
    Car car2 = car1;
    std::cout << "car1._wheel_ptr.get(): " << car1._wheel_ptr.get() << std::endl;
    std::cout << "car2._wheel_ptr.get(): " << car2._wheel_ptr.get() << std::endl;
    return 0;
}
// 输出:
// car1._wheel_ptr.get(): 0x605020
// car2._wheel_ptr.get(): 0x605020
// Car car2 = car1; 这一行调用默认拷贝构造函数,导致car1和car2的_wheel_ptr成员变量指向同一个Wheel对象,这个实际想要的不符,应该每个Car都有属于自己的车轮,所以不能使用默认生成的拷贝构造函数,需要重新实现拷贝构造函数。

#include <iostream>
#include <memory>
class Wheel{
public:
    Wheel(double radius):_radius(radius){}
    double _radius;
};

class Car{
public:
    Car(double wheel_radius){
        _wheel_ptr = std::make_shared<Wheel>(wheel_radius);
    }
    // 需要自己实现拷贝构造函数
    Car(const Car& car){
        _wheel_ptr = std::make_shared<Wheel>(car._wheel_ptr->_radius);
    }
    std::shared_ptr<Wheel> _wheel_ptr;
};

int main(){
    Car car1(10);
    Car car2 = car1;
    std::cout << "car1._wheel_ptr.get(): " << car1._wheel_ptr.get() << std::endl;
    std::cout << "car2._wheel_ptr.get(): " << car2._wheel_ptr.get() << std::endl;
    return 0;
}
// 输出:
// car1._wheel_ptr.get(): 0x605020
// car2._wheel_ptr.get(): 0x605040
// car1 和 car2分别拥有属于自己的Wheel对象。
// 同理 通常情况下,析构函数,拷贝构造函数和赋值运算符都不能使用C++默认生成的,需要自己实现。但是要是数据成员不使用指针,则可以避免这个问题。

3.2 定义对应类型的对象为数据成员

3.2.1 优点

  1. 不需要通过代码进行内存管理
  2. 直接定义成员变量各个成员变量内存连续,有更快的访问速度。但当指针类型成员变量,内存分布不一定连续,更有可能造成cache miss.

3.2.2 缺点

  1. 对应成员不能使用多态。
  2. 需要引入对应类的头文件,降低编译速度。
  3. 每个类实例占用的内存空间更大。

3.3 使用引用类型数据成员

必须在构造函数的初始化列表中初始化引用类型数据成员,不能使用默认构造函数,不能使用默认赋值操作符,引用类型不能被重新赋值。因为这些限制,引用类型很少被用作成员变量。

参考链接

https://stackoverflow.com/questions/8589739/pointer-vs-non-pointer-members-of-a-class
https://stackoverflow.com/questions/38706329/smart-pointers-vs-value-as-member-variables/38706627
https://stackoverflow.com/questions/22146094/why-should-i-use-a-pointer-rather-than-the-object-itself
https://stackoverflow.com/questions/1175646/c-when-should-i-use-a-pointer-member-in-a-class

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值