C++拾趣——Copy Constructor和Assignment Operator的禁用

发布于:2024-08-14 ⋅ 阅读:(143) ⋅ 点赞:(0)

在C++中,拷贝构造函数(Copy Constructor)和赋值函数(Assignment Operator,通常称为赋值运算符重载)是处理对象复制和赋值操作的重要机制。有时候,根据类的设计需要,我们可能会选择禁用或限制这些函数的使用。下面我们来探讨禁用拷贝构造函数和赋值函数的作用、好处以及使用场景。

作用

  1. 资源管理:对于管理如动态分配的内存、文件句柄、网络连接等资源的类,禁止拷贝可以确保资源不会被意外复制,从而避免资源泄露、重复释放等问题。
  2. 保证唯一性:某些对象设计为全局唯一或单例模式时,禁止拷贝可以确保整个程序中只有一个实例。
  3. 避免浅拷贝问题:如果类包含指针成员,默认的拷贝构造函数和赋值运算符会导致浅拷贝,即拷贝的对象和原对象共享相同的指针成员,这可能引起数据不一致或资源释放错误。禁用它们可以促使开发者实现深拷贝或使用智能指针等技术来管理资源。
  4. 提高代码安全性:通过禁用拷贝构造函数和赋值函数,可以减少因不当复制引起的错误和资源泄露问题。
  5. 简化设计:在某些设计模式中,如单例模式或工厂模式,对象的拷贝是不被允许的,禁用这些函数可以使类的设计更加直观和简洁。
  6. 增强类的封装性:通过控制对象的复制行为,可以更好地封装类的内部实现细节,保护对象的完整性。

使用方法

C++11中,引入了对函数/方法的标识符delete,用于告知编译器不要生成对应的函数。这样在编译时,如果对应的函数/方法被使用,就会报错。
我们可以通过以下这个宏来实现Copy Constructor和Assignment Operator的禁用。宏中传递的参数就是类名。

# ifndef NO_COPY_ASSIGN_MACRO_H
# define NO_COPY_ASSIGN_MACRO_H

#define CPP_DISABLE_COPY(...) \
  __VA_ARGS__(const __VA_ARGS__ &) = delete; \
  __VA_ARGS__ & operator=(const __VA_ARGS__ &) = delete;

# endif // NO_COPY_ASSIGN_MACRO_H

使用场景

  1. 资源管理类:如文件操作类、网络连接类、锁类等,这些类通常管理着宝贵的系统资源,不当的拷贝可能导致资源泄露或竞争条件等问题。
    下面这个例子中,ResourceManager 持有一个文件操作类对象file。并且用对象的生命周期来管理这个文件对象的开关。我们不希望复制行为导致文件在其他地方被打开使用。这样我们就可以使用CPP_DISABLE_COPY宏来禁用相关函数,从而控制好资源的使用。
// ResourceManager.h
#ifndef RESOURCE_MANAGER_H
#define RESOURCE_MANAGER_H

#include <iostream>
#include <fstream>
#include "include/macro.h"

class ResourceManager {
public:
    // Constructor to initialize the resource
    ResourceManager(const std::string& filename) {
        openFile(filename);
    }

    // Destructor to clean up the resource
    ~ResourceManager() {
        closeFile();
    }

    // Public method to open a file
    void openFile(const std::string& filename) {
        if (file.is_open()) {
            file.close();
        }
        file.open(filename);
        if (!file) {
            std::cerr << "Failed to open file: " << filename << std::endl;
        }
    }

    // Public method to close the file
    void closeFile() {
        if (file.is_open()) {
            file.close();
        }
    }

    // Public method to read from the file
    std::string readFile() {
        std::string content;
        if (file.is_open()) {
            std::getline(file, content);
        } else {
            std::cerr << "File is not open" << std::endl;
        }
        return content;
    }

private:
    std::fstream file;

    // Delete copy constructor and assignment operator to prevent copies
    CPP_DISABLE_COPY(ResourceManager)
};

#endif // RESOURCE_MANAGER_H
// main.cpp
#include "ResourceManager.h"
#include <iostream>

int main() {
    // Create an instance of ResourceManager
    ResourceManager manager("example.txt");

    // Attempt to copy the ResourceManager instance (should fail)
    // ResourceManager copyManager = manager; // This line should cause a compilation error

    // Attempt to assign one ResourceManager instance to another (should fail)
    // ResourceManager anotherManager("another.txt");
    // anotherManager = manager; // This line should cause a compilation error

    // Attempt to move the ResourceManager instance (should fail)
    // ResourceManager moveManager = std::move(manager); // This line should cause a compilation error

    // Use the ResourceManager to open a file
    manager.openFile("example.txt");

    // Read from the file
    std::string content = manager.readFile();
    std::cout << "File content: " << content << std::endl;

    // Close the file
    manager.closeFile();

    return 0;
}
  1. 单例模式:在单例模式中,类保证一个类仅有一个实例,并提供一个全局访问点。禁止拷贝可以确保这一点。
// Singleton.h
#ifndef SINGLETON_H
#define SINGLETON_H

#include "include/macro.h"

class Singleton {
public:
    // Static method to get the single instance of the class
    static Singleton& getInstance() {
        static Singleton instance; // Guaranteed to be destroyed and instantiated on first use
        return instance;
    }

    // Public method example
    void doSomething() {
        // Implementation of some functionality
    }

private:

    // Delete copy constructor and assignment operator to prevent copies
    CPP_DISABLE_COPY(Singleton)

    // Private constructor to prevent instantiation
    Singleton() {
        // Initialization code
    }

    // Private destructor
    ~Singleton() {
        // Cleanup code
    }
};

#endif // SINGLETON_H
// main.cpp
#include "Singleton.h"
#include <iostream>

int main() {
    // Get the single instance of Singleton
    Singleton& instance1 = Singleton::getInstance();
    Singleton& instance2 = Singleton::getInstance();

    // Attempt to copy the Singleton instance (should fail)
    // Singleton copyInstance = instance1; // This line should cause a compilation error
    
    // Attempt to assign one Singleton instance to another (should fail)
    // Singleton anotherInstance;
    // anotherInstance = instance1; // This line should cause a compilation error

    // Attempt to move the Singleton instance (should fail)
    // Singleton moveInstance = std::move(instance1); // This line should cause a compilation error

    // Demonstrate that both references point to the same instance
    if (&instance1 == &instance2) {
        std::cout << "Both instance1 and instance2 point to the same Singleton instance." << std::endl;
    } else {
        std::cout << "instance1 and instance2 point to different instances." << std::endl;
    }

    // Use the Singleton instance to perform some action
    instance1.doSomething();

    return 0;
}
  1. 线程安全类:对于某些需要保证线程安全的类,拷贝可能导致多线程环境下的状态不一致。禁用拷贝可以避免这种问题。
    比如下面这个类的workerThread成员变量,它表示一个线程。如果这个成员变量随着宿主被复制到很多地方,就会造成管理的混乱。
// ThreadManager.h
#ifndef THREAD_MANAGER_H
#define THREAD_MANAGER_H

#include <thread>
#include <mutex>
#include <iostream>

#include "include/macro.h"

class ThreadManager {
public:
    // Constructor to initialize the thread
    ThreadManager() : threadRunning(false) {}

    // Destructor to clean up the thread
    ~ThreadManager() {
        stopThread();
    }

    // Public method to start the thread
    void startThread() {
        if (!threadRunning) {
            threadRunning = true;
            workerThread = std::thread(&ThreadManager::threadFunction, this);
        }
    }

    // Public method to stop the thread
    void stopThread() {
        if (threadRunning) {
            threadRunning = false;
            if (workerThread.joinable()) {
                workerThread.join();
            }
        }
    }

    // Public method to check if the thread is running
    bool isThreadRunning() const {
        return threadRunning;
    }

private:
    std::thread workerThread;
    std::mutex mtx;
    bool threadRunning;

    // Delete copy constructor and assignment operator to prevent copies
    CPP_DISABLE_COPY(ThreadManager)

    // Private method that runs in the thread
    void threadFunction() {
        while (threadRunning) {
            std::lock_guard<std::mutex> lock(mtx);
            // Perform thread work here
            std::cout << "Thread is running" << std::endl;
        }
    }
};

#endif // THREAD_MANAGER_H
// main.cpp
#include "ThreadManager.h"
#include <iostream>
#include <chrono>
#include <thread>

int main() {
    ThreadManager manager;
    
    // Attempt to copy the ThreadManager instance (should fail)
    // ThreadManager copyManager = manager; // This line should cause a compilation error

    // Attempt to assign one ThreadManager instance to another (should fail)
    // ThreadManager anotherManager;
    // anotherManager = manager; // This line should cause a compilation error

    // Attempt to move the ThreadManager instance (should fail)
    // ThreadManager moveManager = std::move(manager); // This line should cause a compilation error

    // Start the thread
    manager.startThread();
    std::cout << "Thread started" << std::endl;

    // Let the thread run for a while
    std::this_thread::sleep_for(std::chrono::seconds(2));

    // Check if the thread is running
    if (manager.isThreadRunning()) {
        std::cout << "Thread is running" << std::endl;
    }

    // Stop the thread
    manager.stopThread();
    std::cout << "Thread stopped" << std::endl;

    // Check if the thread is running
    if (!manager.isThreadRunning()) {
        std::cout << "Thread is not running" << std::endl;
    }

    return 0;
}
  1. 防止意外拷贝:有些类不希望被拷贝,以避免意外的性能开销或逻辑错误。
    比如下面这个类ExpensiveResource 持有一个vector。如果发生拷贝行为,则会导致vector这样的大内存结构被复制,从而发生性能损耗。我们禁用Copy Constructor和Assignment Operator可以保证类的使用者正确使用该类。
// ExpensiveResource.h
#ifndef EXPENSIVE_RESOURCE_H
#define EXPENSIVE_RESOURCE_H

#include <vector>
#include <iostream>

#include "include/macro.h"

class ExpensiveResource {
public:
    // Constructor to initialize the resource
    ExpensiveResource(size_t size) : data(size) {
        std::cout << "ExpensiveResource created with size " << size << std::endl;
    }

    // Destructor to clean up the resource
    ~ExpensiveResource() {
        std::cout << "ExpensiveResource destroyed" << std::endl;
    }

    // Public method to access the resource
    void doWork() {
        std::cout << "Doing work with ExpensiveResource" << std::endl;
        // Perform operations on the data
    }

private:
    std::vector<int> data; // Simulating an expensive resource


    // Delete copy constructor and assignment operator to prevent copies
    CPP_DISABLE_COPY(ExpensiveResource)
};

#endif // EXPENSIVE_RESOURCE_H
// main.cpp
#include "ExpensiveResource.h"
#include <iostream>

int main() {
    // Create an instance of ExpensiveResource
    ExpensiveResource resource(1000);

    // Attempt to copy the ExpensiveResource instance (should fail)
    // ExpensiveResource copyResource = resource; // This line should cause a compilation error

    // Attempt to assign one ExpensiveResource instance to another (should fail)
    // ExpensiveResource anotherResource(500);
    // anotherResource = resource; // This line should cause a compilation error

    // Attempt to move the ExpensiveResource instance (should fail)
    // ExpensiveResource moveResource = std::move(resource); // This line should cause a compilation error

    // Use the ExpensiveResource to perform some work
    resource.doWork();

    return 0;
}

源码

https://github.com/f304646673/cpulsplus/tree/master/no_copy_assign