Request Short Link
C++ 98
C++ 11
C++ 14
C++ 17
C++ 20
C++ 23
C++ 2c
for-loops as while-loops
array subscription
Show all implicit casts
Show all template parameters of a CallExpr
Use libc++
Transform std::initializer_list
Show noexcept internals
Show padding information
Show coroutine transformation
Show C++ to C transformation
Show object lifetime
Default
15
18
20
22
26
More
GitHub
Patreon
Issues
About
Policies
Examples
C++ Insights @ YouTube
Settings
Version
Workshop: Safe and Efficient C++ for Embedded Environments
×
Made by
Andreas Fertig
Powered by
Flask
and
CodeMirror
Source:
// // The purpose of this code is to // demonstrate an idea for extending // lifetimes of temporary values. // // More precisely, to introduce // non-temporary values behind the scenes // whose values *look* like temporaries. // // The normal way to extend temporaries' // lifetimes is simply not allow them // to be temporaries: use a local variable // instead, whose lifetime will extend // to the end of the current lexical scope // (as in version_2 and version_3 below). // // However, we can imagine scenarios // where we need to initialize the // value at the last possible moment -- // as if it were a temporary after all -- // this code presents a rather contrived // example where a procedure that // consumes two values whose lifetimes // have to be in sync is itself returned // from a different procedure with a // side-effect that affects the object // whose lifetime must be extended. // // Apart from side-effect issues, arguably // for stylistic reasons it would be // preferable *not* to visibly declare // named local variables which are nowhere // used except to pass a value into a // procedure when that value cannot be a // temporary -- in other words, when the // sole purpose of a local variable is // to extend some value's lifetime. // // I'm curious whether it is feasible // to introduce some notation that would // "invisibly" add some local variable // similarly to version_4, only without // the alloca, placement new, and // force_dispose actually being // present in the source code -- maybe // some kind of attribute which could // cause the equivalent of that // extra code to be added silently? // // In this case the lifetime-extended // object is initialized from the string // "ok4", so maybe some annotation // on that string literal? The $$ macro // intends to suggest one notation. In // place of the code as written imagine // instead it just said ` $$ "ok4" ` ... // #include <iostream> #define $$ struct Message { int id; std::string test; Message() : id(get_current_id()) { printf("id = %d (default constructor)\n", id); add_secret_message(); } Message(const char* cs) : id(get_current_id()), test(cs) { printf("id = %d (constructor with literal)\n", id); add_secret_message(); } Message(const Message& m) = delete; Message& operator=(const Message& m) { printf("assignment operator (id = %d, source = %d)\n", id, m.id); test = m.test; return *this; } ~Message() { printf("destructor (id = %d)\n", id); } static void activate(std::string secret_message) { (void) get_secret_message(&secret_message); } static void deactivate() { static std::string clear_string; (void) get_secret_message(&clear_string); } private: void add_secret_message() { test.append( get_secret_message() ); } static int get_current_id() { static int current_id = 0; return ++current_id; } static std::string get_secret_message(std::string* reset = nullptr) { static std::string secret_message; if(reset) { secret_message = *reset; } return secret_message; } }; auto activate = []() { Message::activate( " (inebriated Swedish moose rescued from fermented-apple tree)"); }; auto deactivate = []() { Message::deactivate(); }; const Message& _test(const Message& m1, const Message& m2) { if(m1.test.empty()) return m2; return m1; } auto test = [](auto callback) { callback(); return &_test; }; void version_1() { std::cout << "In version 1 ...\n"; Message m1; // // this is deliberately broken: "ok1" will go out of scope const Message& m2 = test(activate)(m1, "ok1"); std::cout << "m2 = " << m2.test << "\n"; deactivate(); } void version_2() { std::cout << "\nIn version 2 ...\n"; Message m1; Message m1a = "ok2"; // // Initializing m1a ahead of time ensures // that it's lifetime matches m1, but // this is too soon to register the // side-effect of test(activate) const Message& m2 = test(activate)(m1, m1a); std::cout << "m2 = " << m2.test << "\n"; deactivate(); } void version_3() { std::cout << "\nIn version 3 ...\n"; Message m1; Message m1a; // // Here we both ensure m1a has sufficient // lifetime and also reflect the desired // test(activate) side-effect, but at the // cost of additional constructor and // assignment operator calls -- especially // a problem if those too had side-effects const Message& m2 = test(activate)(m1, m1a = "ok3"); std::cout << "m2 = " << m2.test << "\n"; deactivate(); } template<typename T> void force_dispose(void* ptr) { std::cout << "in force_dispose ...\n"; static_cast<T*>(ptr)->T::~T(); } void version_4() { std::cout << "\nIn version 4 ...\n"; Message m1; void* m1a = alloca(sizeof(Message)); // // This is the desired semantics. // The m1a lifetime is extended far enough, // but the Message object is not initialized // until just before it is passed to test_(), // so it does reflect the test(activate) // side-effect. The problem is that it's ugly. const Message& m2 = test(activate)(m1, $$ *new(m1a)Message("ok4") ); std::cout << "m2 = " << m2.test << "\n"; deactivate(); force_dispose<Message>(m1a); } int main() { version_1(); version_2(); version_3(); version_4(); return 0; }
Insight:
Console: