I'm having troubles understanding fully the assignment operator for unique_ptr. I understand that we can only move them, due to the fact that copy constructor and assignment operators are deleted, but what if
a unique_ptr which contains already an allocation is overwritten by a move operation? Is the content previously stored in the smart pointer free'd?
#include <iostream>
#include <memory>
class A{
public:
A() = default;
virtual void act() const {
std::cout << "act from A" << std::endl;
}
virtual ~A() {
std::cout << "destroyed A" << std::endl;
}
};
class B : public A {
public:
B() : A{} {}
void act() const override {
std::cout << "act from B" << std::endl;
}
~B() override {
std::cout << "destroyed from B " << std::endl;
}
};
int main() {
auto pP{std::make_unique<A>()};
pP->act();
==================== ! =======================
pP = std::make_unique<B>(); // || std::move(std::make_unique<B>())
==================== ! =======================
pP->act();
return 0;
}
When I do
pP = std::make_unique<B>();
does it mean that what was allocated in the first lines for pP (new A()) is destructed automatically?
Or should I opt for:
pP.reset();
pP = std::make_unique<B>();
Yes, see section 20.9.1, paragraph 4 of the C++11 draft standard
Additionally, u can, upon request, transfer ownership to another unique pointer u2. Upon completion of
such a transfer, the following postconditions hold:
u2.p is equal to the pre-transfer u.p,
u.p is equal to nullptr, and
if the pre-transfer u.d maintained state, such state has been transferred to u2.d.
As in the case of a reset, u2 must properly dispose of its pre-transfer owned object via the pre-transfer
associated deleter before the ownership transfer is considered complete
In other words, it's cleaning up after itself upon assignment like you'd expect.
Yes, replacing the content of a smart pointer will release the previously-held resource. You do not need to call reset() explicitly (nor would anyone expect you to).
Just for the sake of this particular example. It seems polymorphism in your example didn't allow you to draw clear conclusions from output:
act from A
destroyed A
act from B
destroyed from B
destroyed A
So let's simplify your example and make it straight to the point:
#include <iostream>
#include <memory>
struct A {
explicit A(int id): id_(id)
{}
~A()
{
std::cout << "destroyed " << id_ << std::endl;
}
int id_;
};
int main() {
std::unique_ptr<A> pP{std::make_unique<A>(1)};
pP = std::make_unique<A>(2);
}
which outputs:
destroyed 1
destroyed 2
Online
I hope this leaves no room for misinterpretation.
Related
I am new to boost:asio. I need to pass shared_ptr as argument to handler function.
E.g.
boost::asio::post(std::bind(&::function_x, std::move(some_shared_ptr)));
Is using std::move(some_shared_ptr) correct? or should I use as below,
boost::asio::post(std::bind(&::function_x, some_shared_ptr));
If both are correct, which one is advisable?
Thanks in advance
Regards
Shankar
Bind stores arguments by value.
So both are correct and probably equivalent. Moving the argument into the bind is potentially more efficient if some_argument is not gonna be used after the bind.
Warning: Advanced Use Cases
(just skip this if you want)
Not what you asked: what if function_x took rvalue-reference arguments?
Glad you asked. You can't. However, you can still receive by lvalue reference and just move from that. because:
std::move doesn't move
The rvalue-reference is only there to indicate potentially-moved-from arguments enabling some smart compiler optimizations and diagnostics.
So, as long as you know your bound function is only executed once (!!) then it's safe to move from lvalue parameters.
In the case of shared-pointers there's actually a little bit more leeway, because moving from the shared-ptr doesn't actually move the pointed-to element at all.
So, a little exercise demonstrating it all:
Live On Coliru
#include <boost/asio.hpp>
#include <memory>
#include <iostream>
static void foo(std::shared_ptr<int>& move_me) {
if (!move_me) {
std::cout << "already moved!\n";
} else {
std::cout << "argument: " << *std::move(move_me) << "\n";
move_me.reset();
}
}
int main() {
std::shared_ptr<int> arg = std::make_shared<int>(42);
std::weak_ptr<int> observer = std::weak_ptr(arg);
assert(observer.use_count() == 1);
auto f = std::bind(foo, std::move(arg));
assert(!arg); // moved
assert(observer.use_count() == 1); // so still 1 usage
{
boost::asio::io_context ctx;
post(ctx, f);
ctx.run();
}
assert(observer.use_count() == 1); // so still 1 usage
f(); // still has the shared arg
// but now the last copy was moved from, so it's gone
assert(observer.use_count() == 0); //
f(); // already moved!
}
Prints
argument: 42
argument: 42
already moved!
Why Bother?
Why would you care about the above? Well, since in Asio you have a lot of handlers that are guaranteed to execute precisely ONCE, you can sometimes avoid the overhead of shared pointers (the synchronization, the allocation of the control block, the type erasure of the deleter).
That is, you can use move-only handlers using std::unique_ptr<>:
Live On Coliru
#include <boost/asio.hpp>
#include <memory>
#include <iostream>
static void foo(std::unique_ptr<int>& move_me) {
if (!move_me) {
std::cout << "already moved!\n";
} else {
std::cout << "argument: " << *std::move(move_me) << "\n";
move_me.reset();
}
}
int main() {
auto arg = std::make_unique<int>(42);
auto f = std::bind(foo, std::move(arg)); // this handler is now move-only
assert(!arg); // moved
{
boost::asio::io_context ctx;
post(
ctx,
std::move(f)); // move-only, so move the entire bind (including arg)
ctx.run();
}
f(); // already executed
}
Prints
argument: 42
already moved!
This is going to help a lot in code that uses a lot of composed operations: you can now bind the state of the operation into the handler with zero overhead, even if it's bigger and dynamically allocated.
I'd like to transmit a shared_ptr object via boost asio from a client to a server. Here is my code:
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/asio.hpp>
#include <boost/serialization/export.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <chrono>
#include <iostream>
#include <sstream>
#include <string>
#include <thread>
using namespace std;
class Message {
public:
Message() {
}
virtual ~Message() {
}
string text;
private:
friend class boost::serialization::access;
template <class Archive>
void serialize(Archive &ar, const unsigned int version) {
ar &text;
}
};
BOOST_CLASS_EXPORT(Message)
void runClient() {
// Give server time to startup
this_thread::sleep_for(chrono::milliseconds(3000));
boost::asio::ip::tcp::iostream stream("localhost", "3000");
boost::archive::text_oarchive archive(stream);
for (int i = 0; i < 10; i++) {
std::shared_ptr<Message> dl = std::make_shared<Message>();
stringstream ss;
ss << "Hello " << i;
dl->text = ss.str();
archive << dl;
}
stream.close();
cout << "Client shutdown" << endl;
}
void handleIncommingClientConnection(boost::asio::ip::tcp::acceptor &acceptor) {
boost::asio::ip::tcp::iostream stream;
acceptor.accept(*stream.rdbuf());
boost::archive::text_iarchive archive(stream);
while (true) {
std::shared_ptr<Message> m;
try {
archive >> m;
cout << m->text << endl;
} catch (std::exception &ex) {
cout << ex.what() << endl;
if (stream.eof()) {
cout << "eof" << endl;
stream.close();
cout << "Server: shutdown client handling..." << endl;
break;
} else
throw ex;
}
}
}
void runServer() {
boost::asio::io_service ios;
boost::asio::ip::tcp::endpoint endpoint = boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 3000);
boost::asio::ip::tcp::acceptor acceptor(ios, endpoint);
handleIncommingClientConnection(acceptor);
}
int main(int argc, char **argv) {
thread clientThread(runClient);
thread serverThread(runServer);
clientThread.join();
serverThread.join();
return 0;
}
Here is the program output:
Hello 0
Hello 1
Hello 2
Hello 3
Hello 3
Hello 3
Hello 3
Hello 3
Client shutdown
Hello 3
Hello 3
input stream error
eof
Server: shutdown client handling...
I am expecting the following output:
Hello 0
Hello 1
Hello 2
Hello 3
Hello 4
Hello 5
Hello 6
Hello 7
Client shutdown
Hello 8
Hello 9
input stream error
eof
Server: shutdown client handling...
When changing the shared_ptr to a simple object (std::shared_ptr<Message> m; to Message m) everything works as expected. I want to stick to the shared_ptr. What do I need to change?
Serialization alone seems to work:
stringstream stream;
{
boost::archive::text_oarchive archive(stream);
std::shared_ptr<Message> dl = std::make_shared<Message>();
stringstream ss;
ss << "Hello World!";
dl->text = ss.str();
archive << dl;
}
{
boost::archive::text_iarchive archive(stream);
std::shared_ptr<Message> m;
archive >> m;
cout << m->text << endl;
}
Output: Hello World!
The issues you're encountering are due to object tracking done by Boost.Serialization.
Depending on how the class is used and other factors, serialized
objects may be tracked by memory address. This prevents the same
object from being written to or read from an archive multiple times.
These stored addresses can also be used to delete objects created
during a loading process that has been interrupted by throwing of an
exception.
The documentation actually foreshadows this specific issue happening:
This could cause problems in progams[sic] where the copies of different
objects are saved from the same address.
Furthermore, the Class Serialization Traits documentation on object tracking tells us that in this particular situation, object tracking is enabled:
Default tracking traits are:
For primitive, track_never.
For pointers, track_never. That is, addresses of addresses are not tracked by default.
All current serialization wrappers such as boost::serialization::nvp, track_never.
For all other types, track_selectively. That is addresses of serialized objects are tracked if and only if one or more of the
following is true:
an object of this type is anywhere in the program serialized through a pointer.
the class is explicitly "exported" - see below.
the class is explicitly "registered" in the archive
Going back to your situation -- in the client, due to how your loop body is written, the 5th (and following) Message instance were allocated at the same address as the 4th Message instance. You can verify this by inspecting the values of dl.get() in each iteration. (In my tests on coliru, all of the instances were allocated at the same address, so YMMV).
Due to how object tracking works, all those shared_ptr instances were considered to point to the same Message instance (even though you changed the value meanwhile -- the library does not expect this happening), so the additional occurrences were just serialized as additional references. Upon deserialization... to be honest this smells of memory leaks and/or dangling reference issues (opinion, haven't investigated this in detail).
Summed up, the main issue with the code as shown is that it breaks a prerequisite of the serialization library, which is that you're serializing some constant state, and on deserialization you recreate that same state.
One way to address this would be to have an initialized std::vector of shared_ptr<Message> containing all the messages to transmit in this particular transaction. Similarly, you'd deserialize the whole vector on the other side. If you expect to have some persistent connection, then add framing to the protocol, with each frame containing an archive that contains one sequence of messages.
Minimal code modifications to make this work -- add include
#include <boost/serialization/vector.hpp>
Change runClient() as such:
void runClient() {
// Give server time to startup
this_thread::sleep_for(chrono::milliseconds(3000));
boost::asio::ip::tcp::iostream stream("127.0.0.1", "3000");
std::vector<std::shared_ptr<Message>> messages;
for (int i = 0; i < 10; i++) {
std::shared_ptr<Message> dl = std::make_shared<Message>();
stringstream ss;
ss << "Hello " << i;
dl->text = ss.str();
messages.emplace_back(dl);
}
boost::archive::text_oarchive archive(stream);
archive << messages;
stream.close();
cout << "Client shutdown" << endl;
}
And change handleIncommingClientConnection(...) as such:
void handleIncommingClientConnection(boost::asio::ip::tcp::acceptor &acceptor) {
boost::asio::ip::tcp::iostream stream;
acceptor.accept(*stream.rdbuf());
boost::archive::text_iarchive archive(stream);
while (true) {
try {
std::vector<std::shared_ptr<Message>> messages;
archive >> messages;
for (auto const& m : messages) {
cout << m->text << endl;
}
} catch (std::exception &ex) {
cout << ex.what() << endl;
if (stream.eof()) {
cout << "eof" << endl;
stream.close();
cout << "Server: shutdown client handling..." << endl;
break;
} else
throw ex;
}
}
}
NB: This doesn't add any support for multiple frames -- the client is expected to close the connection after it sent one vector of messages, otherwise the behaviour is undefined.
Sample on Coliru
Further resources:
boost serialization multiple objects
I've got the following test.cpp file
#include <string>
#include <functional>
#include <unordered_map>
#include <iostream>
class Mystuff {
public:
std::string key1;
int key2;
public:
Mystuff(std::string _key1, int _key2)
: key1(_key1)
, key2(_key2)
{}
};
namespace std {
template<>
struct hash<Mystuff *> {
size_t operator()(Mystuff * const& any) const {
size_t hashres = std::hash<std::string>()(any->key1);
hashres ^= std::hash<int>()(any->key2);
std::cout << "Hash for find/insert is [" << hashres << "]" << std::endl;
return (hashres);
}
};
}; /* eof namespace std */
typedef std::unordered_map<Mystuff *, Mystuff *>mystuff_map_t;
mystuff_map_t map;
int insert_if_not_there(Mystuff * stuff) {
std::cout << "Trying insert for " << stuff->key1 << std::endl;
if (map.find(stuff) != map.end()) {
std::cout << "It's there already..." << std::endl;
return (-1);
} else {
map[stuff] = stuff;
std::cout << "Worked..." << std::endl;
}
return (0);
}
int main(){
Mystuff first("first", 1);
Mystuff second("second", 2);
Mystuff third("third", 3);
Mystuff third_duplicate("third", 3);
insert_if_not_there(&first);
insert_if_not_there(&second);
insert_if_not_there(&third);
insert_if_not_there(&third_duplicate);
}
You can compile with g++ -o test test.cpp -std=gnu++11.
I don't get what I'm doing wrong with it: the hash keying algorithm is definitely working, but for some reason (which is obviously in the - bad - way I'm doing something), third_duplicate is inserted as well in the map, while I'd wish it wasn't.
What am I doing wrong?
IIRC unordered containers need operator== as well as std::hash. Without it, I'd expect a compilation error. Except that your key is actually MyStuff* - the pointer, not the value.
That means you get the duplicate key stored as a separate item because it's actually not, to unordered_map, a real duplicate - it has a different address, and address equality is how unordered_map is judging equality.
Simple solution - use std::unordered_map<Mystuff,Mystuff> instead. You will need to overload operator== (or there's IIRC some alternative template, similar to std::hash, that you can specialize). You'll also need to change your std::hash to also accept the value rather than the pointer.
Don't over-use pointers in C++, especially not raw pointers. For pass-by-reference, prefer references to pointers (that's a C++-specific meaning of "reference" vs. "pointer"). For containers, the normal default is to use the type directly for content, though there are cases where you might want a pointer (or a smart pointer) instead.
I haven't thoroughly checked your code - there may be more issues than I caught.
As I understand it, one cannot change the reference variable once it has been initialized. See, for instance, this question. However, here is a minmal working example which sort of does reassign it. What am I misunderstanding? Why does the example print both 42 and 43?
#include <iostream>
class T {
int x;
public:
T(int xx) : x(xx) {}
friend std::ostream &operator<<(std::ostream &dst, T &t) {
dst << t.x;
return dst;
}
};
int main() {
auto t = T(42);
auto q = T(43);
auto &ref = t;
std::cerr << ref << std::endl;
ref = q;
std::cerr << ref << std::endl;
return 0;
}
You're not changing the reference here.
You are replacing the object the reference is referring to.
In other words: after the assignment, your t is replaced by q.
ref is still a reference to t.
That does not perform a reference reassignment. Instead, it copy assigns the object in variable q into the object referenced by ref (which is t in your example).
This also justifies why you got 42 as output: the default copy assignment operator modified the first object.
Given a class A with two constructors, taking initializer_list<int> and initializer_list<initializer_list<int>> respectively, then
A v{5,6};
calls the former, and
A v{{5,6}};
calls the latter, as expected. (clang3.3, apparently gcc behaves differently, see the answers. What does the standard require?)
But if I remove the second constructor, then A v{{5,6}}; still compiles and it uses the first constructor. I didn't expect this.
I thought that A v{5,6} would be the only way to access the initializer_list<int> constructor.
(I discovered this while playing around with std::vector and this question I asked on Reddit, but I created my own class A to be sure that it wasn't just a quirk of the interface for std::vector.)
I think this answer might be relevant.
Yes, this behaviour is intended, according to §13.3.1.7 Initialization
by list-initialization
When objects of non-aggregate class type T are list-initialized (8.5.4), overload resolution selects the constructor in two phases:
— Initially, the candidate functions are the initializer-list constructors (8.5.4) of the class T and the argument list consists of
the initializer list as a single argument.
— If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all
the constructors of the class T and the argument list consists of the
elements of the initializer list.
In gcc I tried your example. I get this error:
error: call of overloaded 'A(<brace-enclosed initializer list>)' is ambiguous
gcc stops complaining if I use three sets of brace. i.e.:
#include <iostream>
#include <vector>
#include <initializer_list>
struct A {
A (std::initializer_list<int> il) {
std::cout << "First." << std::endl;
}
A (std::initializer_list<std::initializer_list<int>> il) {
std::cout << "Second." << std::endl;
}
};
int main()
{
A a{0}; // first
A a{{0}}; // compile error
A a2{{{0}}}; // second
A a3{{{{0}}}}; // second
}
In an attempt to mirror the vector's constructors, here are my results:
#include <iostream>
#include <vector>
#include <initializer_list>
struct A {
A (std::initializer_list<int> il) {
std::cout << "First." << std::endl;
}
explicit A (std::size_t n) {
std::cout << "Second." << std::endl;
}
A (std::size_t n, const int& val) {
std::cout << "Third." << std::endl;
}
A (const A& x) {
std::cout << "Fourth." << std::endl;
}
};
int main()
{
A a{0};
A a2{{0}};
A a3{1,2,3,4};
A a4{{1,2,3,4}};
A a5({1,2,3,4});
A a6(0);
A a7(0, 1);
A a8{0, 1};
}
main.cpp:23:10: warning: braces around scalar initializer
A a2{{0}};
^~~
1 warning generated.
First.
First.
First.
First.
First.
Second.
Third.
First.