Currently, I'm storing a collection of std::unique_ptrs to heap allocated objects of a polymorphic type:
struct Foo {
virtual ~Foo() = default;
};
Collection<std::unique_ptr<Foo>> foos;
The basic interface I need is putting / taking owners of Foo to / from foos. The objects stored in foos are never supposed to be nullptr so I'd like to replace runtime assert(owner_taken) with compile-time checks. Moreover, I would like to be capable of using non-null owners in the context where a nullable one may be expected.
Probably, I need to store something like unique_ref but how would I extract one from foos? I don't want a copy, I want the stored object itself, so owner->clone() isn't a solution. Neither I can std::move(owner) because the state of a "unique reference" would be invalid afterwards.
Is there a clean design decision?
Is there a never-null unique owner of heap allocated objects?
There is no such type in the standard library.
It is possible to implement such type. Simply define a type with unique_ptr member and mark the default constructor deleted. You can mark constructor from std::nullptr_t deleted also so that construction from nullptr will be prevented at compile time.
Whether you construct from an external pointer, or allocate in the constructor, there is no alternative for checking for null at runtime.
Reading your question, I interpret the following requirements:
You don't want to copy or move the object itself (Foo)
You don't want a wrapper which has some sort of empty state which excludes move semantics
The object itself (Foo) should be stored on the heap such that its lifetime is independent of the control flow
The object itself (Foo) should be deleted once it is not used any more
As construction / destruction, copy and move are the only ways to get objects into / out of a container, the only thing left is a wrapper object which is copied when moved into / out of the container.
You can create such an object yourself as follows:
// LICENSE: MIT
#include <memory>
#include <utility>
template<typename T>
class shared_ref {
public:
template<typename ...Args>
shared_ref(Args&&... args)
: data{new T(std::forward<Args>(args)...)}
{}
shared_ref(shared_ref&&) = delete;
shared_ref& operator=(shared_ref&&) = delete;
T& get() noexcept {
return *data;
}
const T& get() const noexcept {
return *data;
}
operator T&() noexcept {
return get();
}
operator const T&() const noexcept {
return get();
}
void swap(shared_ref& other) noexcept {
return data.swap(other);
}
private:
std::shared_ptr<T> data;
};
template<class T>
void swap(shared_ref<T>& lhs, shared_ref<T>& rhs) noexcept {
return lhs.swap(rhs);
}
I leave it as an exercise to the reader to add support for Allocator, Deleter, operator[], implicit conversion contructor to base classes.
This can then be used as follows:
#include <iostream>
int main() {
shared_ref<int> r; // default initialized
std::cout << "r=" << r << std::endl;
r = 5; // type conversion operator to reference
std::cout << "r=" << r << std::endl;
shared_ref<int> s{7}; // initialized with forwarded arguments
std::cout << "s=" << s << std::endl;
std::swap(r, s);
std::cout << "r=" << r << ", " << "s=" << s << std::endl;
s = r; // copy assignment operator
std::cout << "s=" << s << std::endl;
const shared_ref<int> t{s}; // copy constructor
std::cout << "t=" << t << std::endl;
//t = 8; // const ref from a const object is immutable
return 0;
}
Related
Is there a way to avoid copying when initializing a vector?
The code below will produce the following output.
#include <iostream>
#include <vector>
using namespace std;
struct Ticker {
std::string m_ticker;
Ticker() {
std::cout << "Default constructor" << std::endl;
}
Ticker(const std::string& ticker)
: m_ticker(ticker)
{
std::cout << "Parametrized constructor" << std::endl;
}
Ticker(Ticker&& other)
{
std::cout << "Move constructor" << std::endl;
m_ticker = other.m_ticker;
other.m_ticker = "";
}
Ticker(const Ticker& x)
{
std::cout << "Copy constructor" << std::endl;
m_ticker = x.m_ticker;
}
~Ticker()
{
std::cout << "Destructor" << std::endl;
}
friend std::ostream& operator << (std::ostream& os, const Ticker& dr);
};
std::ostream& operator << (std::ostream& os, const Ticker& dr)
{
os << "|" << dr.m_ticker << "|";
return os;
}
int main() {
std::vector<Ticker> table = std::move(std::vector<Ticker>{std::move(Ticker("MSFT")), std::move(Ticker("TSL"))});
for (const auto& row: table)
{
std::cout << row << std::endl;
}
return 0;
}
This produces the following output:
Parametrized constructor
Move constructor
Parametrized constructor
Move constructor
Copy constructor
Copy constructor
Destructor
Destructor
Destructor
Destructor
|MSFT|
|TSL|
Destructor
Destructor
Is there a way to avoid the copy constructor and initialize in-place or just move without copying?
If you use
std::vector<Ticker> table = std::vector<Ticker>{Ticker("MSFT"), Ticker("TSL")};
You will get
Parametrized constructor
Parametrized constructor
Copy constructor
Copy constructor
Destructor
Destructor
|MSFT|
|TSL|
Destructor
Destructor
Which has 4 constructor calls instead of the 6 you currently have. 2 of those calls are for Ticker("MSFT") and Ticker("TSL") and then the additional two copies are because initializer lists store the elements in them as const, so they have to be copied into the vector as you can't move from a const object.
To get the bare minimum of 2 constructor calls you'll need to use the emplace_back member function like
std::vector<Ticker> table; // create empty vector
table.reserve(2); // allocate space for 2 Tickers but create nothing
table.emplace_back("MSFT"); // directly construct from "MSFT" in the reserved space
table.emplace_back("TSL"); // directly construct from "TSL" in the reserved space
which has the output of
Parametrized constructor
Parametrized constructor
|MSFT|
|TSL|
Destructor
Destructor
If you want a syntax like std::vector<Ticker> table = std::vector<Ticker>{Ticker("MSFT"), Ticker("TSL")};, but without the extra overhead, you could wrap the emplace_back solution in a factory function like
template <typename T, typename... Args>
auto make_vector(Args&&... args)
{
std::vector<T> data;
data.reserve(sizeof...(Args));
(data.emplace_back(std::forward<Args>(args)), ...);
return data;
}
and then you would use it like
auto table = make_vector<Ticker>("MSFT", "TSL");
When running the following code:
struct Copy
{
Copy() {std::cerr << __PRETTY_FUNCTION__ << std::endl;}
Copy(const Copy & other) {std::cerr << __PRETTY_FUNCTION__ << std::endl;}
Copy & operator=(const Copy & other) {std::cerr << __PRETTY_FUNCTION__ << std::endl; return *this;}
Copy(Copy && other) {std::cerr << __PRETTY_FUNCTION__ << std::endl;}
Copy & operator=(Copy && other) {std::cerr << __PRETTY_FUNCTION__ << std::endl; return *this;}
~Copy() {std::cerr << __PRETTY_FUNCTION__ << std::endl;}
};
char buffer[1024];
template <typename Type>
Type * push(Type value)
{
return new(buffer) Type(std::move(value));
};
int main()
{
push(Copy());
return 0;
}
I get the following output:
Copy::Copy()
Copy::Copy(Copy &&)
Copy::~Copy()
Is there anyway to elide the move constructor?
I was hoping that with -O3, it would be constructed in place, but by my testing, that doesn't seem to be the case.
Is there anyway to elide the move constructor? [...] I was hoping that with -O3, it would be constructed in place,
Well, you're explicitly calling the move constructor... and the object is being constructed in-place in buffer. Why do you expect the move constructor call to be elided?
template <typename Type>
Type * push(Type value)
{
// invokes `Type::Type(Type&&)`
// vvvvvvvvvvvvvvvvvvvvvv
return new(buffer) Type(std::move(value));
// ^^^^^^^^^^^^^^^^
// casts `value` to `Type&&`
};
Your question would make more sense if you were trying to construct Copy with a value of some other type. E.g.:
struct Copy
{
Copy(int) { std::cout << "INT\n"; }
// ... other ctors ...
};
template <typename Type, typename Arg>
Type * push(Arg x)
{
return new(buffer) Type(std::move(x));
};
int main()
{
push<Copy>(1);
}
The code above prints:
INT
No move/copy constructor is invoked.
live example on wandbox
I do not think you can do this. Because elision requires the compiler to have an intrinsic knowledge of where the objects are being constructed. And with that information, it can just avoid moves and copies and simply place the object where it needs to go. For example when you return something from the stack of one function back to another, the compiler can elide the move/copy.
But in your case placement new allows you to construct an object into any arbitrary buffer. And that buffer can really be anywhere, for example it can be on the stack (like in your case) or it can be allocated on the heap with new. So the compiler does not elide the move/copy here.
Strictly speaking, this can happen through some analysis of the code since the compiler already knows where the buffer is, but I doubt most compilers implement this.
Note note that you have declared an array of character pointers and not characters, so the buffer is more than 1024 bytes in length if that is what you were expecting
Note Also note that calling std::move explicitly can prevent elision
The best you can do in this case is make an in place constructor or a move constructor (as you have above) to construct that object into the buffer. An in place constructor would look something like this
template <typename... args>
void construct(std::in_place_t, Args&&... args) {
new (buffer) Type{std::forward<Args>(args)...};
}
Use an emplace function with perfect argument forwarding. There are a few more things to say since you're about to embark on an adventure in placement new:
Use std::aligned_storage_t<> for the storage. It guarantees that your objects will be properly aligned.
Do read and use the return value of placement new. In simple cases it won't be different from the address you provide. However the standard allows it to be, and in complex class hierarchies it might be.
updated example:
#include <iostream>
struct Copy
{
Copy() {std::cerr << __PRETTY_FUNCTION__ << std::endl;}
Copy(const Copy & other) {std::cerr << __PRETTY_FUNCTION__ << std::endl;}
Copy & operator=(const Copy & other) {std::cerr << __PRETTY_FUNCTION__ << std::endl; return *this;}
Copy(Copy && other) {std::cerr << __PRETTY_FUNCTION__ << std::endl;}
Copy & operator=(Copy && other) {std::cerr << __PRETTY_FUNCTION__ << std::endl; return *this;}
~Copy() {std::cerr << __PRETTY_FUNCTION__ << std::endl;}
};
std::aligned_storage_t<sizeof(Copy), alignof(Copy)> buffer[4];
template <typename Type, typename LegalStorage, typename...Args>
auto emplace(LegalStorage* buffer, Args&&...args) -> Type*
{
return new(buffer) Type(std::forward<Args>(args)...);
};
int main()
{
auto p1 = emplace<Copy>(buffer /* constructor arguments go here*/);
auto p2 = emplace<Copy>(&buffer[1]/* constructor arguments go here*/);
auto p3 = emplace<Copy>(buffer + 2/* constructor arguments go here*/);
auto p4 = emplace<Copy>(buffer + 3/* constructor arguments go here*/);
return 0;
}
Can someone explain why the original object that is passed to a new object via std::move is still valid afterwards?
#include <iostream>
class Class
{
public:
explicit Class(const double& tt) : m_type(tt)
{
std::cout << "defaultish" << std::endl;
};
explicit Class(const Class& val) :
m_type(val.m_type)
{
std::cout << "copy" << std::endl;
};
explicit Class(Class&& val) :
m_type(val.m_type)
{
m_type = val.m_type;
std::cout << "move: " << m_type << std::endl;
};
void print()
{
std::cout << "print: " << m_type << std::endl;
}
void set(const double& tt)
{
m_type = tt;
}
private:
double m_type;
};
int main ()
{
Class cc(3.2);
Class c2(std::move(cc));
c2.print();
cc.set(4.0);
cc.print();
return 0;
}
It outputs the following:
defaultish
move: 3.2
print: 3.2
print: 4
I would expect the calls to cc.set() and cc.print() to fail...
UPDATE
Thanks to answers below, we've identified that 1) I wasn't moving anything in the move constructor, and 2) std::move() on an int or double doesn't do anything because it's more expensive to move these types than to simply copy. The new code below updates the class's private member variable to be of type std::string instead of a double, and properly calls std::move when setting this private member variable in the Class' move constructor, resulting in an output that shows how a std::move results in a valid but unspecified state
#include <iostream>
#include <string>
class Class
{
public:
explicit Class(const std::string& tt) : m_type(tt)
{
std::cout << "defaultish" << std::endl;
};
explicit Class(const Class& val) :
m_type(val.m_type)
{
std::cout << "copy" << std::endl;
};
explicit Class(Class&& val) : m_type(std::move(val.m_type))
{
std::cout << "move: " << m_type << std::endl;
};
void print()
{
std::cout << "print: " << m_type << std::endl;
}
void set(const std::string val )
{
m_type = val;
}
private:
std::string m_type;
};
int main ()
{
Class cc("3.2");
Class c2(std::move(cc));
c2.print( );
cc.print();
cc.set( "4.0" );
cc.print();
return 0;
}
And finally the output:
defaultish
move: 3.2
print: 3.2
print:
print: 4.0
Because the standard says so.
Moved-from objects have a valid but unspecified state. That means you can still use them, but you can't be sure what state they'll be in. They could look just as they did before the move, depending on what is the most efficient way to "move" data out of them. For example, "moving" from an int makes no sense (you'd have to do extra work to reset the original value!) so a "move" from an int is actually only ever going to be a copy. The same is true of a double.
Although in this case it's got more to do with the fact that you didn't actually move anything.
In the code example, std::move determines which constructor gets called. Nothing more. So c2(std::move(cc)) calls the move constructor for Class. The move constructor for Class doesn't do anything to its argument, so cc is unchanged, and it can be used just as it could have before the call to the move constructor.
All the talk in comments and answers about the state of an object that has been moved from is about the requirements on standard library types, which will be left in a "valid but unspecified state" (17.6.5.15, [lib.types.movedfrom]). What you do with your types is not affected by that.
EDIT: sigh. You edited the code and changed the question. Now that your class holds a std::string instead of a float things are different, and the std::string object in cc is, indeed, in a "valid but unspecified state".
I'm currently writing a logging class (just for practice) and ran into an issue. I have two classes: The class Buffer acts as a temporary buffer and flushes itself in it's destructor. And the class Proxy that returns a Buffer instance, so I don't have to write Buffer() all the time.
Anyways, here is the code:
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
class Buffer
{
private:
std::stringstream buf;
public:
Buffer(){};
template <typename T>
Buffer(const T& v)
{
buf << v;
std::cout << "Constructor called\n";
};
~Buffer()
{
std::cout << "HEADER: " << buf.str() << "\n";
}
Buffer(const Buffer& b)
{
std::cout << "Copy-constructor called\n";
// How to get rid of this?
};
Buffer(Buffer&&) = default;
Buffer& operator=(const Buffer&) & = delete;
Buffer& operator=(Buffer&&) & = delete;
template <typename T>
Buffer& operator<<(const T& v)
{
buf << v;
return *this;
}
};
class Proxy
{
public:
Proxy(){};
~Proxy(){};
Proxy(const Proxy&) = delete;
Proxy(Proxy&&) = delete;
Proxy& operator=(const Proxy&) & = delete;
Proxy& operator=(Proxy&&) & = delete;
template <typename T>
Buffer operator<<(const T& v) const
{
if(v < 0)
return Buffer();
else
return Buffer(v);
}
};
int main () {
Buffer(Buffer() << "Test") << "what";
Buffer() << "This " << "works " << "fine";
const Proxy pr;
pr << "This " << "doesn't " << "use the copy-constructor";
pr << "This is a " << std::setw(10) << " test";
return 0;
}
Here is the output:
Copy-constructor called
HEADER: what
HEADER: Test
HEADER: This works fine
Constructor called
HEADER: This doesn't use the copy-constructor
Constructor called
HEADER: This is a test
The code does exactly what I want but it depends on RVO. I read multiple times that you should not rely on RVO so I wanted to ask how I can:
Avoid RVO completely so that the copy constructor is called every time
Avoid the copy constructor
I already tried to avoid the copy constructor by returning a reference or moving but that segfaults. I guess thats because the temporary in Proxy::operator<< is deleted during return.
I'd also be interested in completely different approaches that do roughly the same.
This seems like a contrived problem: Firstly, the code works whether RVO is enabled or disabled (you can test it by using G++ with the no-elide-constructors flag). Secondly, the way you are designing the return of a Buffer object for use with the << operator can only be done by copying†: The Proxy::operator<<(const T& v) function creates a new Buffer instance on the stack, which is then deleted when you leave the function call (i.e. between each concatenation in pr << "This " << "doesn't " << "use the copy-constructor";); This is why you get a segmentation fault when trying to reference this object from outside the function.
Alternatively, you could define a << operator to use dynamic memory by e.g. returning a unique_ptr<Buffer>:
#include <memory>
...
std::unique_ptr<Buffer> operator<<(const T& v) const
{
if(v < 0)
return std::unique_ptr<Buffer>(new Buffer());
else
return std::unique_ptr<Buffer>(new Buffer(v));
}
However, your original concatenation statements won't be compilable, then, because Proxy::operator<<(const T& v) now returns an object of type std::unique_ptr<Buffer> rather than Buffer, meaning that this returned object doesn't have its own Proxy::operator<<(const T& v) function defined and so multiple concatenations won't work without first explicitly de-referencing the returned pointer:
const Proxy pr;
std::unique_ptr<Buffer> pb = pr << "This ";
// pb << "doesn't " << "use the copy-constructor"; // This line doesn't work
*pb << "doesn't " << "use the copy-constructor";
In other words, your classes rely inherently on copying and so, if you really want to avoid copying, you should throw them away and completely re-design your logging functionalities.
† I'm sure there's some black-magic voodoo which can be invoked to make this possible --- albeit at the cost of one's sanity.
I wanted to restrict a specific class to be creatable on the stack only (not via allocation). The reason for this is that on the stack, the object which lifetime has begun last, will be the first to be destroyed, and I can create a hierarchy. I did it like this:
#include <cstddef>
#include <iostream>
class Foo {
public:
static Foo createOnStack() {
return {};
}
~Foo () {
std::cout << "Destructed " << --i << std::endl;
}
protected:
static int i;
Foo () {
std::cout << "Created " << i++ << std::endl;
}
Foo (const Foo &) = delete;
};
int Foo::i = 0;
The constructor normally should push the hierarchy stack, and the destructor pops it. I replaced it here for proof of concept. Now, the only way you can use such an object is by storing it in a temporary reference like this:
int main() {
Foo && a = Foo::createOnStack();
const Foo& b = Foo::createOnStack();
return 0;
}
My question now is, how safe is this with the C++ standard? Is there still a way to legally create a Foo on the heap or hand it down from your function into another frame (aka return it from your function) without running into undefined behaviour?
EDIT: link to example https://ideone.com/M0I1NI
Leaving aside the protected backdoor, C++17 copy elision breaks this in two ways:
#include<iostream>
#include<memory>
struct S {
static S make() {return {};}
S(const S&)=delete;
~S() {std::cout << '-' << this << std::endl;}
private:
S() {std::cout << '+' << this << std::endl;}
};
S reorder() {
S &&local=S::make();
return S::make();
}
int main() {
auto p=new S(S::make()),q=new S(S::make()); // #1
delete p; delete q;
reorder(); // #2
}
The use of new is obvious and has been discussed.
C++17 also allows prvalues to propagate through stack frames, which means that a local can get created before a return value and get destroyed while that return value is alive.
Note that the second case already existed (formally in C++14 and informally long before) in the case where local is of type S but the return value is some other (movable) type. You can't assume in general that even automatic object lifetimes nest properly.