[原创]解决有依赖关系的多个结构体初始化问题

一,问题背景

在C++编程中,结构体(struct)是一种常用的复合数据类型,用于将不同的数据项组合成一个单一的数据结构。当结构体之间存在继承或其他依赖关系时,如何正确地声明和定义它们变得尤为重要。如果处理不当,可能会导致编译错误,如多重定义或未定义引用。

二,知识点

1. 依赖关系

识别并确认结构体之间的依赖关系是解决问题的第一步。例如,结构体B继承自结构体A,或者结构体B包含结构体A的实例。

2. 声明与定义的剥离

是指将结构体的声明和定义分开放置在不同的文件中。

在头文件(.h 文件)中进行结构体的声明,包括结构体的名称、成员变量类型和方法的声明,但不包含具体的实现代码。

在源文件(.cpp 文件)中进行结构体的定义,包括为结构体分配内存空间、初始化成员变量等具体操作。

这种剥离的好处在于可以将结构体的实现细节隐藏起来,使得头文件中只包含必要的信息,避免了源文件的修改对整个项目的影响,同时也可以提高编译速度和代码的模块化程度。

3. 前向声明

前向声明是指在使用一个结构体之前,提前声明该结构体的存在和类型,但不包含其完整的定义。前向声明只是告诉编译器这个结构体的名称和类型,但并不提供有关其成员变量和方法的具体信息。

1)使用场景:
适用于解决循环依赖的问题,即两个或多个结构体相互依赖的情况。
不适用于需要知道完整定义的情况,如创建对象、调用成员函数等。
不适用于模板类和函数,因为模板的定义必须在第一次使用前完全可见。

2)区别:
前向声明通常用于类和指针,特别是当存在循环依赖时。
声明与定义的剥离适用于任何需要分离接口和实现的场合,以提高编译效率。

三,解决:使用默认构造函数和初始化函数

举例:从基础到复杂

  1. 最基础的一个结构体:SpecificArea
//  表示一个矩形框的左上角和右下角坐标
struct SpecificArea{
    float xmin;
    float ymin;
    float xmax;
    float ymax;
};
  1. 结构体 A 使用了 SpecificArea,结构体 B 继承自 A 并且添加了一个新属性:
// 最基础的配置
struct A{
    SpecificArea rect;
    float conf;
    float iou;
};
// 在最基础的配置上添加
struct B:A{
    std::vector<uint16_t> threshold{2};
};
  1. 这种情况下如何声明一个 B 类型的结构体并将其赋值默认数值? ——添加不同参数的默认构造函数
// 定义一个结构体SpecificArea,用于表示矩形区域的左下角和右上角坐标
struct SpecificArea {
    float xmin; // 矩形区域左下角的x坐标
    float ymin; // 矩形区域左下角的y坐标
    float xmax; // 矩形区域右上角的x坐标
    float ymax; // 矩形区域右上角的y坐标

    // SpecificArea的默认构造函数,初始化所有坐标为0.0f
    SpecificArea() : xmin(0.0f), ymin(0.0f), xmax(0.0f), ymax(0.0f) {}
};

// 定义一个结构体A,作为更基础的结构体
struct A {
    SpecificArea rect; // 表示矩形区域的SpecificArea对象
    float conf;       // 表示置信度的浮点数
    float iou;        // 表示交并比的浮点数

    // A的默认构造函数,初始化rect为默认构造的SpecificArea对象,conf和iou为0.0f
    A() : rect(), conf(0.0f), iou(0.0f) {}
};

// 定义一个结构体B,继承自A,并添加了额外的属性
struct B : A {
    std::vector<uint16_t> thresholds; // 存储阈值的动态数组,初始化为{2}个元素,均为0

    // B的默认构造函数,使用A的默认构造函数初始化基类部分,thresholds初始化为{0, 0}
    B() : A(), thresholds({0, 0}) {}

    // B的带参数构造函数,接受一个A类型的引用,用以初始化B中的基类部分
    // thresholds同样初始化为{0, 0}
    B(const A& base) : A(base), thresholds({0, 0}) {}
};

int main() {
    B temp; // 创建B类型的临时对象temp
    std::cout << temp.thresholds[0] << std::endl; // 输出temp对象中thresholds数组的第一个元素
}

四,补充知识点:拷贝函数

1. 定义/用处:

拷贝构造函数是一种特殊的构造函数,用于创建一个新对象,该新对象是通过拷贝一个已经存在的对象来初始化的。在C++中,拷贝构造函数的定义形式通常如下。

// 其中,ClassName 是类的名称,other 是对现有对象的引用。
ClassName(const ClassName& other);

拷贝构造函数会根据现有对象 other 的值来初始化新对象的成员变量。

2. 使用场景

拷贝构造函数的主要作用是:

  1. 对象初始化:使用一个已有对象初始化一个新对象时。
ClassName obj1;
ClassName obj2 = obj1; // 调用拷贝构造函数
  1. 按值传递对象:函数参数按值传递时。
void func(ClassName obj); // 调用拷贝构造函数
  1. 返回对象:当对象以值返回的方式从函数返回时。
ClassName func() {
    ClassName obj;
    return obj; // 调用拷贝构造函数
}
3. 默认拷贝构造函数/自动生成的拷贝构造函数

如果一个类没有显式定义拷贝构造函数,编译器会自动生成一个默认的拷贝构造函数。这个默认的拷贝构造函数会逐个成员地进行浅拷贝,即按成员的类型逐个拷贝成员变量的值。

4. 深拷贝/自定义拷贝构造函数

如果类中有指针成员,使用默认的浅拷贝可能会导致多个对象共享相同的内存地址,进而产生资源管理问题(如重复释放内存)。此时,需要用户定义一个深拷贝构造函数,以确保每个对象都有自己的内存副本。

拷贝构造函数是一个用于拷贝现有对象的特殊构造函数。它在创建新对象时,通过拷贝现有对象的成员变量值来初始化新对象。如果类包含复杂成员(如指针),需要深拷贝时,就需要自定义拷贝构造函数。

实现拷贝构造函数时应注意:

  1. 确保正确分配新内存并复制值。
  2. 处理自我赋值的情况(虽然一般在赋值运算符中更常见,但在某些复杂情境下也可能在拷贝构造函数中需要处理)。
  3. 释放资源时避免内存泄漏。
#include <iostream>
using namespace std;

class Example {
private:
    int* data;
public:
    // 构造函数
    Example(int value) {
        data = new int(value);
        cout << "Constructor called" << endl;
    }

    // 拷贝构造函数
    Example(const Example &obj) {
        data = new int(*(obj.data));
        cout << "Copy Constructor called" << endl;
    }

    // 析构函数
    ~Example() {
        delete data;
        cout << "Destructor called" << endl;
    }

    // 显示数据
    void display() const {
        cout << "Value: " << *data << endl;
    }
};

int main() {
    Example obj1(10);    // 调用构造函数
    Example obj2 = obj1; // 调用拷贝构造函数

    obj1.display();
    obj2.display();

    return 0;
}

在上面的例子中,obj2 是通过拷贝 obj1 创建的,因此调用了拷贝构造函数,并且为 data 成员分配了新的内存。

拷贝构造函数在管理动态内存时尤为重要,通过正确实现深拷贝,确保对象间的独立性,从而避免潜在的内存管理问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值