【C++进阶学习】第十四弹——特殊类设计——探寻各种情况下类的应用

2024-08-29 12:24:49 浏览数 (2)

前言:

C 类是C 很重要的一个部分,在很多应用场景中都发挥着十分重要的作用,今天我们来讲解几个特殊场景下类的应用

一、特殊类:只能在栈/堆上创建对象

在C 中,对象的创建位置(栈或堆)对于程序的性能和内存管理有着重要影响。栈上创建的对象通常生命周期短,而堆上创建的对象可以拥有更长的生命周期。下面我们就来详细探讨如何设计一个类,使其对象只能在堆上或栈上创建,以及这些设计选择背后的逻辑和实践。

1. 只在栈上创建对象

设计策略:通过私有构造函数和公共静态工厂方法来实现。

实现步骤

  1. 私有构造函数:将构造函数声明为私有,禁止外部直接调用。
  2. 公共静态工厂方法:提供一个公共的静态方法,用于创建并返回对象的指针。

代码示例

代码语言:javascript复制
class MyClass {
private:
    MyClass() {} // 私有构造函数,禁止外部直接调用
public:
    static MyClass* CreateInstance() {
        return new MyClass();
    }
    // 其他成员函数和数据成员
};

优点

  • 防止了外部代码直接创建对象,有助于控制对象的生命周期和管理。
  • 通过静态工厂方法,可以提供统一的接口来创建对象,简化了代码结构。

缺点

  • 需要外部代码调用静态工厂方法来创建对象,可能增加了调用成本。
  • 对于复杂的对象创建逻辑,静态工厂方法可能不够灵活。

2. 只在堆上创建对象

设计策略:使用智能指针(如 std::unique_ptrstd::shared_ptr)来管理对象的生命周期。

实现步骤

  1. 智能指针:将对象的创建和管理委托给智能指针。
  2. 对象的创建:在需要使用对象的代码中,通过调用智能指针的构造函数来创建对象。

代码示例

代码语言:javascript复制
#include <memory>

class MyClass {
public:
    MyClass() {
        // 初始化代码
    }
    // 其他成员函数和数据成员
};

int main() {
    std::unique_ptr<MyClass> myObject = std::make_unique<MyClass>(); // 在堆上创建对象
    // 使用myObject
    return 0;
}

优点

  • 自动管理内存,避免了内存泄漏的风险。
  • 提供了异常安全的内存管理,即使在异常抛出时也能正确释放资源。

缺点

  • 与直接使用指针相比,可能引入额外的开销(如智能指针的额外检查)。
  • 对于需要频繁创建和销毁对象的场景,可能会增加性能开销。

总结

设计类以控制对象的创建位置,主要考虑了内存管理的效率、代码的可读性和可维护性。私有构造函数结合公共静态工厂方法适用于控制对象生命周期的场景,而使用智能指针则适用于需要自动内存管理的场景。选择哪种策略取决于具体的应用场景和需求,以及对性能、安全性和代码结构的权衡。

二、特殊类:不能被继承

1. 使用 final 关键字

在 C 中,final 关键字可以用来声明一个类或者成员函数,使其不能被进一步继承。

1.1 声明一个不可继承的类
代码语言:javascript复制
class Base {
public:
    virtual void doSomething() = 0; // 纯虚函数,使得类成为抽象类
};

class Derived final : public Base {
    // 这里尝试继承 Base 类将会导致编译错误
};

在这个例子中,Derived 类通过继承自 Base 类,并且使用了 final 关键字,使得 Derived 类本身也不能被继承。

1.2 声明一个不可继承的成员函数
代码语言:javascript复制
class Base {
public:
    virtual void doSomething() = 0;

    virtual void finalize() final {
        // 不可覆盖的成员函数
    }
};

在这个例子中,finalize 成员函数被声明为 final,这意味着它不能被派生类覆盖。

2. 使用 private 访问控制

将一个类声明为私有(private)可以防止外部代码创建该类的实例,但并不能阻止继承。为了防止继承,可以将基类的构造函数和析构函数设置为私有。

2.1 私有构造函数和析构函数
代码语言:javascript复制
class Base {
private:
    Base() {} // 私有构造函数
    ~Base() {} // 私有析构函数

public:
    virtual void doSomething() = 0;
};

class Derived : public Base {
    // 这里尝试继承 Base 类将会导致编译错误
};

在这个例子中,由于 Base 类的构造函数和析构函数都是私有的,因此它不能被继承。

3. 使用 deleted 关键字

在 C 11 及以后的版本中,可以使用 deleted 关键字来声明一个不能被继承的类。

3.1 使用 deleted 关键字
代码语言:javascript复制
class Base {
public:
    virtual void doSomething() = 0;

    virtual ~Base() = default;

    class Derived final : public Base {
        // 这里尝试继承 Base 类将会导致编译错误
    };
};

class DeletedBase {
public:
    class Derived : public DeletedBase {
        // 这里尝试继承 DeletedBase 类将会导致编译错误
    };
};

在这个例子中,DeletedBase 类的 Derived 类继承尝试会导致编译错误,因为 DeletedBase 类被声明为不能被继承。

总结

通过以上方法,我们可以在 C 中设计不可继承的类。使用 final 关键字是最直接的方式,而使用 private 访问控制或 deleted 关键字则可以提供更灵活的解决方案。在实际应用中我们可以通过场景和设计要求来选择最合适的方法

三、特殊类:单例模式

单例模式是一种常用的设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取这个实例。在 C 中,有多种方法可以实现单例模式。

下面主要讲两种实现方法:饿汉式和懒汉式

1. 单例模式概述

单例模式的主要目的是控制一个类的实例化过程,确保在任何时候,只有一个实例被创建,并且全局所有的地方都可以通过同一个引用访问这个实例。

2. 实现单例模式的基本方法

2.1 饿汉式

饿汉式单例模式在类加载时就立即初始化单例对象。

代码语言:javascript复制
class Singleton {
private:
    static Singleton instance; // 静态成员变量

    Singleton() {} // 私有构造函数

public:
    static Singleton& getInstance() {
        return instance; // 返回单例实例
    }

    // 其他成员函数
};

Singleton Singleton::instance; // 静态成员变量的初始化

在这个例子中,Singleton 类的构造函数是私有的,外部无法直接创建其实例。通过 getInstance 方法,我们可以获取到类的唯一实例。

2.2 懒汉式

懒汉式单例模式在第一次使用时才创建实例。

代码语言:javascript复制
class Singleton {
private:
    static Singleton* instance; // 静态成员指针

    Singleton() {} // 私有构造函数

public:
    static Singleton* getInstance() {
        if (instance == nullptr) {
            instance = new Singleton(); // 第一次调用时创建实例
        }
        return instance; // 返回单例实例
    }

    static void releaseInstance() {
        if (instance != nullptr) {
            delete instance;
            instance = nullptr;
        }
    }

    // 其他成员函数
};

Singleton* Singleton::instance = nullptr; // 静态成员指针的初始化

在这个例子中,getInstance 方法检查实例是否已经创建,如果没有,则创建一个新的实例。这种方法在第一次使用时才创建实例,但需要注意内存泄漏的问题。

2.3 饿汉式(线程安全)

(这个涉及到线程安全的问题,如果还没有学习线程,可以先跳过这一部分)

在多线程环境下,懒汉式可能会出现问题,因为多个线程可能同时进入 if 判断,导致创建多个实例。为了解决这个问题,可以使用互斥锁(mutex)来保证线程安全。

代码语言:javascript复制
#include <mutex>

class Singleton {
private:
    static Singleton* instance;
    static std::mutex mutex;

    Singleton() {} // 私有构造函数

public:
    static Singleton* getInstance() {
        if (instance == nullptr) {
            std::lock_guard<std::mutex> lock(mutex);
            if (instance == nullptr) {
                instance = new Singleton();
            }
        }
        return instance; // 返回单例实例
    }

    static void releaseInstance() {
        std::lock_guard<std::mutex> lock(mutex);
        if (instance != nullptr) {
            delete instance;
            instance = nullptr;
        }
    }

    // 其他成员函数
};

Singleton* Singleton::instance = nullptr; // 静态成员指针的初始化
std::mutex Singleton::mutex; // 互斥锁的初始化

在这个例子中,我们使用了 std::mutex 来确保在多线程环境下,单例实例只被创建一次。

总结

单例模式在 C 中有多种实现方式,包括饿汉式、懒汉式以及线程安全的懒汉式。选择哪种方式取决于具体的应用场景和需求。在设计单例类时,需要注意线程安全,特别是在多线程环境中使用懒汉式单例模式时。

四、总结

以上就是C 部分特殊类的设计问题,总之,在不同的场景下,类可以通过不同的设计形式来实现特殊的功能,更多的特殊类的设计方式等待我们继续去探讨

0 人点赞