You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

遵循Rule of Three:为何删除拷贝构造/赋值之一留隐式定义易出错?

Why Deleting Only One Copy Operation Causes Errors for Resource-Managing Classes

Great question! Let's break down why this mistake leads to bugs, using concrete examples to make it clear.

Background

Classes that manage non-copyable resources (like file handles, network connections, or hardware access) rely on a "handle" (e.g., a pointer or file descriptor) to interact with the resource. The resource itself can't be duplicated—you can't make an independent copy of an open file handle that points to the same file state, for instance. So the class's copy semantics need to align with this: either disable copying entirely, or implement meaningful deep copy (which is often impossible or undesirable for these resources).

Why Partial Deletion Breaks Things

If you delete only one copy operation (say, the copy constructor) but leave the other (copy assignment) to be implicitly generated, the compiler creates a shallow copy for the remaining operation. A shallow copy just copies the handle's value (like a pointer address) instead of duplicating the underlying resource. This leads to two critical issues:

  1. Double-free/resource corruption: When both objects go out of scope, they'll both try to release the same resource.
  2. Unexpected shared state: Changes to the resource via one object will affect the other, since they share the same handle.

Example 1: Deleting Copy Constructor but Keeping Implicit Copy Assignment

#include <cstdio>
#include <iostream>

class FileHandler {
private:
    FILE* file;
public:
    // Open a file on construction
    FileHandler(const char* filename) : file(std::fopen(filename, "w")) {
        if (!file) std::cerr << "Failed to open file!\n";
    }

    // Close the file on destruction
    ~FileHandler() {
        if (file) {
            std::fclose(file);
            std::cout << "Closed file handle\n";
        }
    }

    // Delete copy constructor, but leave copy assignment implicit
    FileHandler(const FileHandler&) = delete;

    // Write content to the file
    void write(const char* msg) {
        if (file) std::fputs(msg, file);
    }
};

int main() {
    FileHandler f1("test.txt");
    FileHandler f2("temp.txt");

    f2 = f1; // Implicit copy assignment does a shallow copy of the FILE*

    f1.write("Hello from f1\n");
    f2.write("Hello from f2\n");

    // When main exits:
    // 1. f2 is destroyed first, closing the file handle
    // 2. f1 is destroyed next, trying to close the already closed handle
    // This is UNDEFINED BEHAVIOR (crash, corruption, or silent failure)
    return 0;
}

What's Wrong Here?

  • The implicit copy assignment copies the FILE* pointer from f1 to f2, so both objects now point to the same open file.
  • f2's original file handle (for temp.txt) is overwritten without being closed—this is a resource leak.
  • When the program ends, both f1 and f2 try to close the same file handle. Closing an already closed descriptor is undefined behavior, which can crash your program or cause system-level issues.

Example 2: Deleting Copy Assignment but Keeping Implicit Copy Constructor

#include <cstdio>
#include <iostream>

class FileHandler {
private:
    FILE* file;
public:
    FileHandler(const char* filename) : file(std::fopen(filename, "w")) {
        if (!file) std::cerr << "Failed to open file!\n";
    }

    ~FileHandler() {
        if (file) {
            std::fclose(file);
            std::cout << "Closed file handle\n";
        }
    }

    // Delete copy assignment, but leave copy constructor implicit
    FileHandler& operator=(const FileHandler&) = delete;

    void write(const char* msg) {
        if (file) std::fputs(msg, file);
    }
};

int main() {
    FileHandler f1("test.txt");
    FileHandler f2 = f1; // Implicit copy constructor does a shallow copy

    f1.write("Hello from f1\n");
    f2.write("Hello from f2\n");

    // Again, both objects will try to close the same handle on destruction
    return 0;
}

What's Wrong Here?

  • The implicit copy constructor creates f2 as a shallow copy of f1, so both share the same file handle.
  • When main exits, both f1 and f2 destroy the same resource, leading to the same double-free undefined behavior as before.
  • Any writes to either object affect the same file, which is almost never what you intend when copying a resource handler.

The Fix: Disable Both Copy Operations (or Implement Proper Semantics)

For classes managing non-copyable resources, the correct approach is to disable both the copy constructor and copy assignment operator (using = delete), or implement meaningful copy semantics if possible (though for most non-copyable resources, deep copy isn't feasible).

Here's the corrected version of the FileHandler class:

#include <cstdio>
#include <iostream>

class FileHandler {
private:
    FILE* file;
public:
    FileHandler(const char* filename) : file(std::fopen(filename, "w")) {
        if (!file) std::cerr << "Failed to open file!\n";
    }

    ~FileHandler() {
        if (file) {
            std::fclose(file);
            std::cout << "Closed file handle\n";
        }
    }

    // Disable both copy operations
    FileHandler(const FileHandler&) = delete;
    FileHandler& operator=(const FileHandler&) = delete;

    // Optional: Enable move semantics if you want to transfer ownership
    FileHandler(FileHandler&&) noexcept = default;
    FileHandler& operator=(FileHandler&&) noexcept = default;

    void write(const char* msg) {
        if (file) std::fputs(msg, file);
    }
};

This way, you can't accidentally create copies of the handler, which prevents resource sharing and double-free issues entirely.

内容的提问来源于stack exchange,提问作者sunshilong369

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 19:57:30