-2

A common pattern I use is:

const string& GetConstString() {
  static const auto* my_string = new string("useful const string");
  return *my_string;
}

[This is not a leak! See this video] This resolves many lifetime issues. string can be replaced with any type with a nontrivial dtor.

If you had a type with a default ctor & trivial dtor, you could simply do

const MyType& GetConstMyType() {
  static MyType my_type;
  return my_type;
}

I was working with a class that has a default ctor and trivial dtor. And I wondered whether that class would be default- or value- initialized. It turns out, it doesn't really matter for class-types. So this becomes an academic question [eg if you had an array of this class].

But would it be default- or value- initialized?

wrhall
  • 1,288
  • 1
  • 12
  • 26
  • 4
    Why `new` and where and when do you call `delete`? – juanchopanza Feb 20 '18 at 21:07
  • 3
    Statics are first zero-initialized, and then initialized in whatever other way that you specified. Therefore, even if you default-initialize and that does nothing (like for scalars), you still get zero-initialization. – Kerrek SB Feb 20 '18 at 21:07
  • 1
    I don't understand your question. Which initialization is used depends on which initializer you use. – Rakete1111 Feb 20 '18 at 21:07
  • 1
    @juanchopanza In this case, it's not necessarily a bad idea to simply "leak" the memory. Titus Winters talks about it in one of his CppCon talks about Abseil, but in short, using `new` means that you no longer have to worry about destruction-order and accessing `my_string` after it's been destroyed from another thread – Justin Feb 20 '18 at 21:11
  • @Justin: Threads are a reasonable concern, but "don't bother destroying the object" is a pretty blunt approach to that problem. – Lightness Races in Orbit Feb 20 '18 at 21:13
  • @Justin Yeah, I have heard that argument. I don't buy it. I'm not sure of the exact situations where leaking is undefined behaviour, but I'd rather not have to think about that. – juanchopanza Feb 20 '18 at 21:14
  • 1
    @juanchopanza [Leaking memory is not UB](https://stackoverflow.com/a/1978859/1896169). I'm also very sure that not-calling-a-destructor is also not UB, but I haven't found a reference for that yet – Justin Feb 20 '18 at 21:18
  • 2
    @Justin Consider objects with destructors that do more than just release memory. Perhaps an object that flushes buffers on destruction. It's best to get in the habit of not leaking objects that don't care about it, as that's how you find yourself doing it for an object that does care. – François Andrieux Feb 20 '18 at 21:18
  • @Justin IIRC It is UB if the destructor has side-effects. – juanchopanza Feb 20 '18 at 21:18
  • 1
    @juanchopanza Looking it up, yes it's [UB if the destructor has side-effects that the program depends on](http://eel.is/c++draft/basic.life#5). For `std::string`, it would be totally fine. `~string` is roughly just a call to `delete`. – Justin Feb 20 '18 at 21:33
  • 1
    @Justin Sure. But this "pattern" requires knowing for sure that the destructor won't cause UB. So it may "solve" some and create others. – juanchopanza Feb 20 '18 at 21:59
  • @LightnessRacesinOrbit I think std::quick_exit [http://en.cppreference.com/w/cpp/utility/program/quick_exit ] would be better, but some codebases may not be able to use it. The object will be cleaned up by the OS as soon as the binary terminates anyway - it's a bit silly for your program to waste time cleaning up globals right before the OS reclaims the memory. – wrhall Feb 20 '18 at 22:31
  • @juanchopanza if your dtor has side-effects [and you don't know it, eg I'd expect an RAII file handle to, but...], you probably have more than just this bug. – wrhall Feb 20 '18 at 22:34
  • @wrhall Ehm, no, that makes no sense. I won't code differently if I use `std::fstream` in my code. Because I don't do stuff like leak on purpose. – juanchopanza Feb 20 '18 at 22:37
  • @juanchopanza yes, but if you don't realize your dtor has side-effects, then you won't care where destruction happens. And that seems bug-prone. Eg imagine an absl::MutexLock - https://abseil.io/docs/cpp/guides/synchronization#mutexes-and-invariants - if you don't care about where its destructor is called you're gonna have a bad time. This feels true in general for dtors with side-effects. – wrhall Feb 20 '18 at 22:42
  • @wrhall I use C++, which gives me full control of object lifetime. I don't use "patterns" where object lifetime is not well defined. So I don't have to worry about this kind of thing. Your bug is that you're using a global variable basically. That's where the trouble you try to fix by leaking starts. – juanchopanza Feb 20 '18 at 22:45
  • Ignoring RAII UB and the other bits and bobs for the moment, what I don't like is this is the extra noise when searching for a memory leak you DO care about. – user4581301 Feb 20 '18 at 22:49
  • @user4581301 I don't believe LSAN [run as part of ASAN] reports it as a memory leak: https://github.com/google/sanitizers/wiki/AddressSanitizerLeakSanitizer. If you use Valgrind, you'll just need to figure out how to suppress the warning for this type of non-leak. – wrhall Feb 21 '18 at 14:41

2 Answers2

6

I don't see what lifetime issues the pointer solves. In fact, it adds one: a memory leak.

You should use the second version, and it will (ultimately) be initialised just like it would without the static keyword.

const string& GetConstString()
{
   // Initialised on first use; destroyed properly on program exit
   static const std::string my_string("useful const string");
   return my_string;
}

This has the added benefit of not double-dynamic-allocating.

More generally, which specific type of initialisation is used depends on what you write in your code.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • 1
    Whether or not this is a memory leak is debatable. When the program is exiting, not calling `delete` isn't a huge concern; the OS will clean up the resources for you. Also, you always have this one pointer to the memory, so some would say it's not a memory leak. Even if you do consider it to be a memory leak, it only leaks a constant amount of memory, so it's not necessarily a problem. – Justin Feb 20 '18 at 21:14
  • 6
    How does the OP's first example leak memory? Exactly one instance of the string can ever be created, and the pointer is never lost. – Solomon Slow Feb 20 '18 at 21:15
  • 1
    @Justin: How is it debatable? You `new`d something, and never called `delete` on the pointer you got back. – Lightness Races in Orbit Feb 20 '18 at 21:15
  • 2
    @jameslarge: The reason you don't want to lose the pointer is that, if you do, you can't `delete` it. If you keep the pointer but don't `delete` it anyway, you still have the same problem. – Lightness Races in Orbit Feb 20 '18 at 21:17
  • 2
    If you keep a pointer to something, and you never delete it, you don't have a _problem_, you have a pointer to something you can use. In this case, you have a pointer to a "useful const string." "Memory leak" means losing the ability to use things that you failed to deallocate. – Solomon Slow Feb 20 '18 at 21:21
  • 2
    @jameslarge: You're missing the point, which is that you `new`d something then did not ever `delete` it. That is a textbook memory leak. The definition of, even. – Lightness Races in Orbit Feb 20 '18 at 21:21
  • 2
    In that case, every program that I have ever worked on had a memory leak. But fortunately, the majority of them only had the _good_ kind of memory leak. – Solomon Slow Feb 20 '18 at 21:23
  • 1
  • Will a program like valgrind see this use of `new` as a memory leak? In that case I would avoid it. – Kevin Feb 20 '18 at 21:29
  • @Kevin Yes, Valgrind tends to report this use of `new`. I think you can pass it some configuration that says not to, but I've never found out how – Justin Feb 20 '18 at 21:31
  • 1
    @LightnessRacesinOrbit "How is it debatable?" It depends on how you define memory leak. Titus Winters talks about this exact case and whether it's considered a leak in [this talk](https://youtu.be/xu7q8dGvuwk?t=38m13s). He gives motivation about why you might want to use this technique at [34m41s](https://youtu.be/xu7q8dGvuwk?t=34m41s). I do agree that this technique may not be the best approach, but there are reasons to consider it – Justin Feb 20 '18 at 21:42
  • First: using a function resolves the fact that order of initialization of globals across translation units is unspecified [per https://stackoverflow.com/questions/3746238/c-global-initialization-order-ignores-dependencies/3746249#3746249]. Second, using a pointer means we don't have to worry about which thread destructs the string. In particular, there may be UB where threads access the variable after it has been destructed. This isn't an issue if the destructor is trivial. That's why I separated the two cases. Your example exhibits UB in a multithreaded environment. – wrhall Feb 20 '18 at 22:07
  • 1
    But this doesn't even answer the question, except to say it "depends ... what you write in your code." What are the variables I control here? What would make it default vs value initialization? – wrhall Feb 20 '18 at 22:09
  • 2
    @wrhall: The exact same as would make it for a non-`static` - it's instantiating an object, with the same syntax as you'd use elsewhere. There seems to be a misapprehension about what the `static` keyword does. – Lightness Races in Orbit Feb 20 '18 at 22:19
  • Aha, interesting! Thanks! [it does feel a bit magic to me :P ] – wrhall Feb 20 '18 at 22:20
0

Per a comment from "Lightness Races in Orbit", the answer is:

A static object will be default/value initialized with the exact same rules as for a non-static object.

wrhall
  • 1,288
  • 1
  • 12
  • 26