Ch14: The Rule of Five: Copy, Move, Swap

English

提示:本页尚未翻译,当前显示英文原文。

1. Why This Chapter Exists

Every class in Chapter 13 owned numbers. Copying one was obviously harmless: point b{a} gives you two points, and nothing is shared. account owned a balance, and copying it was still harmless.

The moment a class owns something the operating system handed out — a file descriptor, a socket, a lock, a block of memory — that comfort ends. Two objects that own the same descriptor are a bug waiting to happen, and the compiler's default behaviour makes exactly that bug: it copies the number, not the resource.

Such a class has to answer five questions, and this chapter is those five answers.

2. What a Resource Is

A resource is anything that

A file descriptor is the smallest useful example. ::open returns an int; ::close gives it back; the process has a limited number of them; and the int is a handle, not the file. Copying the handle does not copy the file — it gives you a second name for the same thing, and two owners of one resource is precisely the problem this chapter solves.

3. The Five Special Member Functions

When you do not write them, the compiler writes them for you. When you write one, the rest change their meaning. They are:

Member Called when Subchapter
destructor ~T() an object goes out of scope, or is deleted Ch14.2
copy constructor T(T const&) a new object is made from an existing one Ch14.3
copy assignment operator=(T const&) an existing object is given another object's value Ch14.4
move constructor T(T&&) a new object is made from one that is about to die Ch14.5
move assignment operator=(T&&) an existing object takes over from one that is about to die Ch14.6
swap you ask for it by name Ch14.7

Five of them are special members the compiler will generate; swap is not — nobody calls it unless you write the call. It belongs here anyway, because it is the one operation that lets copy assignment be safe (Ch14.8), and because ::std::swap falls back to three moves, which is only fast and safe when your moves are noexcept.

4. The Example: my_posix_file

Every page from here on uses one running example: a class that owns a POSIX file descriptor. It is a reduced version of what fast_io itself does in basic_posix_file, keeping only what the five rules need, so that the interesting code is never more than fifteen lines.


class my_posix_file
{
	int fd_{-1};

public:
	my_posix_file() noexcept = default;
	explicit my_posix_file(::fast_io::cstring_view path, ::std::int_least32_t flags) throws;
	my_posix_file(my_posix_file const &other) throws;
	my_posix_file &operator=(my_posix_file const &other) throws;
	my_posix_file(my_posix_file &&other) noexcept;
	my_posix_file &operator=(my_posix_file &&other) noexcept;
	~my_posix_file() noexcept;
	void swap(my_posix_file &other) noexcept;

	[[nodiscard]] bool is_open() const noexcept;
	[[nodiscard]] ::std::int_least32_t native_handle() const noexcept;
	::std::size_t read_some(char *buf, ::std::size_t n) throws;
	::std::size_t write_some(char const *buf, ::std::size_t n) throws;
};

The path is a ::fast_io::cstring_view, the flags are the plain POSIX int flags (O_RDONLY, O_WRONLY | O_CREAT, ...), and the body calls ::open, ::dup and ::close directly. Nothing is hidden behind fast_io helpers, so every rule this chapter states can be checked against the code on the page.

Failures are reported the only way this tutorial reports failures (Ch3.16):


if (fd == -1) [[unlikely]]
{
	throw throws static_cast<::std::errc>(errno);
}

On a POSIX system the standard ::std::errc values are the errno values, so the cast preserves the error exactly; the handler sees [posix](2)No such file or directory and friends.

5. The Two Ideas That Pay for the Chapter

The rules themselves are bookkeeping. Two ideas make them worth learning, and they are the reason this chapter exists at all.

The critical line

Inside a function that may fail, there is a line — sometimes an obvious one, sometimes a single statement — such that

Put every fallible step above the line and every change below it, and the operation either happens completely or not at all. That is the strong exception safety guarantee, and it is what Ch14.4 is really about.

Four levels of exception safety

nothrow, strong, basic, none — every function you write has exactly one of these, whether you thought about it or not. Ch14.9 names them, classifies every member of my_posix_file, and shows how herbceptions turn the first level from a promise into something the compiler proves.

6. Conventions Used in This Chapter

7. Contents