I want to make sure it unwinds properly if something goes wrong during memory allocation in it's constructor.
Then you must make your code conform to the idea of "one class controls one resource or does one job"
#include <memory>
// foo has one job - to manage fooness.
// It no longer manages memory resources
class foo
{
public:
foo()
: var1(new int(1))
, var2(new int(2))
{
}
std::unique_ptr<int> var1; // unique_ptr only manages a resource
std::unique_ptr<int> var2; // unique_ptr only manages a resource
};
other correct initialisation forms:
#include <memory>
// foo has one job - to manage fooness.
// It no longer manages memory resources
class foo
{
public:
foo()
: var1(std::make_unique<int>(1))
, var2(std::make_unique<int>(2))
{
}
std::unique_ptr<int> var1; // unique_ptr only manages a resource
std::unique_ptr<int> var2; // unique_ptr only manages a resource
};
default values:
#include <memory>
// foo has one job - to manage fooness.
// It no longer manages memory resources
class foo
{
public:
// default constructor now implicit
std::unique_ptr<int> var1 = std::make_unique<int>(1); // unique_ptr only manages a resource
std::unique_ptr<int> var2 = std::make_unique<int>(2); // unique_ptr only manages a resource
};
Correct, but idiomatically unpleasant - redundant initialisation:
#include <memory>
// foo has one job - to manage fooness.
// It no longer manages memory resources
class foo
{
public:
foo()
: var1(nullptr)
, var2(nullptr)
{
var1 = std::make_unique<int>(1);
var2 = std::make_unique<int>(2);
}
std::unique_ptr<int> var1; // unique_ptr only manages a resource
std::unique_ptr<int> var2; // unique_ptr only manages a resource
};
Here's one way to do it without composition. Note all the extra complexity for no gain:
#include <memory>
// foo now has two jobs - to manage fooness, and to manage resources.
// This adds un-necessary complication, bugs and maintenance overhead
class foo
{
public:
foo()
: var1(nullptr)
, var2(nullptr)
{
var1 = new int(1);
var2 = new int(2);
}
foo(const foo& other)
: var1(nullptr)
, var2(nullptr)
{
if (other.var1) {
var1 = new int(*(other.var1));
}
if (other.var2) {
var2 = new int(*(other.var2));
}
}
foo(foo&& other) noexcept
: var1(other.var1)
, var2(other.var2)
{
other.var1 = nullptr;
other.var2 = nullptr;
}
foo& operator=(const foo& other)
{
foo tmp(other);
std::swap(var1, tmp.var1);
std::swap(var2, tmp.var2);
return *this;
}
foo& operator=(foo&& other) noexcept
{
foo tmp(std::move(other));
std::swap(var1, tmp.var1);
std::swap(var2, tmp.var2);
return *this;
}
~foo() noexcept
{
delete var1;
delete var2;
}
int* var1; // doesn't manage anything
int* var2; // doesn't manage anything
};