A few example of customizing constructor, destructor, copy and move operations.

Just constructor

Most of the time, you only need to write a constructor.

Here is a sample class that implements a thread pool.

It assumes there is a class stoppable_thread which requires a void() function in the constructor (and starts the thread), and with a destructor that signals to stop and joins the thread.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include "stoppable_thread.h"
#include <list>

class thread_pool
{
public:
  thread_pool(size_t size, std::function<void()> fn)
  {
    for (size_t i; i < size, ++i)
    {
      threads_.emplace_back(fn);
    }
  }

private:
  std::list<stoppable_thread> threads_;
};

The way it works is simple. Other than the constructor, the thread_pool class relies on the behaviour of the member variable.

So when the class is destructed, it relies on the list to destroy its elements (and stop the threads in the thread pool).

If we try to copy the class, the compiler will try to copy the list. The list will try to copy the values of type stopable_thread, and most likely this is non-copiable, hence it will fail at compile time.

If we try to move the class, the compiler will try to move the list. That will essentially just move the list sentinel pointers, so it will work.

Which leaves us with what happens if the thread_pool constructor fails in the body, e.g. if it creates a few threads, but if fails for one. Then that will throw, but the compiler will destroy the list. This will destroy its elements, stopping the threads that were created before failing.

Buffer

Here is a buffer class that can be copied and moved. This is just an example. For production code you should consider using std::vector instead.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
// correctnes is easier to ensure by using
// a class helper just for the pointer lifetime
struct buffer_impl
{
  char * p;

  explicit buffer_impl(size_t size = 0) :
    p{ new char[size] }
  {
  }

  ~buffer_impl()
  {
    delete[] p;
  }

  buffer_impl(buffer_impl && other) noexcept :
    p{ other.p }
  {
    other.p = nullptr;
  }

  buffer_impl & operator=(buffer_impl && other) noexcept
  {
    // for the sake of example
    // use temporary
    // to deal with move self-assignment
    // e.g. a = std::move(a)
    char * temp = other.p;
    other.p = nullptr;
    delete[] p;
    p = temp;

    return *this;
  }

  void set_contents(const buffer_impl & other, size_t size) noexcept
  {
    for(size_t i = 0 ; i < size ; ++i)
    {
      p[i] = other.p[i];
    }
  }
};

// using the class above the buffer becomes
class buffer
{
  buffer_impl impl_;
  size_t size_;

public:
  explicit buffer(size_t size = 0) :
    impl_{ size },
    size_{ size }
  {
  }

  // destructor not required, default does the job

  buffer(const buffer & other) :
    impl_{ other.size_ },
    size_{ other.size_ }
  {
    impl_.set_contents(other.impl_, other.size_);
  }

  buffer & operator=(const buffer & other)
  {
    buffer_impl x{ other.size_ };
    x.set_contents(other.impl_, other.size_);

    impl_ = std::move(x);
    size_ = other.size_;

    return *this;
  }

  // defaulting move is required because we defined copy
  buffer(buffer && other) noexcept = default;
  buffer & operator=(buffer && other) noexcept = default;

  // more methods here ...
};

Non copyable

Here is a utility class that can be derived from to delete the copy constructor and copy assignment (same idea as boost::noncopyable).

1
2
3
4
5
6
7
8
struct noncopyable
{
  noncopyable(const noncopyable &) = delete;
  noncopyable & operator=(const noncopyable &) = delete;
protected:
  noncopyable() { }
  ~noncopyable() { }
};

Resource template

Here is a template to wrap resources to ensure they get closed/released. Usually such resources can’t be copied (we use noncopyable for this), but can be moved.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include "noncopyable.h"

template<class resource_traits>
struct unique_resource :
  private noncopyable
{
  explicit unique_resource(
    typename resource_traits::value_type x = resource_traits::invalid_value
    ) noexcept :
    value{ x }
  {
  }

  ~unique_resource()
  {
    if (is_valid())
    {
      resource_traits::close(value);
    }
  }

  unique_resource(unique_resource && other) noexcept :
    value{ other.value }
  {
    other.value = resource_traits::invalid_value;
  }

  unique_resource & operator=(unique_resource && other) noexcept
  {
    // for the sake of example
    // use test for (this != &other)
    // to deal with move self-assignment
    // e.g. a = std::move(a)
    if (this != &other)
    {
      if (is_valid())
      {
        resource_traits::close(value);
      }
      value = other.value;
      other.value = resource_traits::invalid_value;
    }
    return *this;
  }

  bool is_valid() noexcept
  {
    return (resource_traits::invalid_value != value);
  }

  typename resource_traits::value_type value;
};

And here is a sample usage to handle FILE * using a slim RAII approach.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include "unique_resource.h"
#include <cstdio>
#include <stdexcept>
#include <iostream>

struct file_resource_traits
{
  using value_type = FILE *;
  static constexpr value_type invalid_value = nullptr;
  static void close(value_type x) noexcept { fclose(x); }
};

using file = unique_resource<file_resource_traits>;

file open_a_file()
{
  file x{ fopen("a.cpp", "r") };
  if (!x.is_valid())
  {
    throw std::runtime_error("Failed to open file");
  }
  return x;
}

int main()
{
  try
  {
    file x{ open_a_file() };
    // use x.value ...
  }
  catch(const std::exception & e)
  {
    std::cerr << "Exception: " << e.what() << std::endl;
    return 1;
  }
  std::cout << "All good!\n";
  return 0;
}