2

I am trying to optimize for speed of execution a piece of code using the factory design pattern.

The factory will produce many objects of a class having some members that are constant throughtout the execution of the program, and some members that are not. I always initialize the constant members with literals.

My question is, how is the memory of the constant members going to be managed? Can the compiler optimize the code so that this memory is not allocated/deallocated each time I create/destroy a new object?

Ideally I would like the literals to reside on a fixed piece of memory and only the reference to this memory be given to each new object. I would like to avoid using global variables.

class Test {
private:
  const std::string &name_;
  int value_;
public:
  Test(const std::string &name, int value) : name_(name), value_(value) {}
};

int main() {
  Test test1("A", 1);
  Test test2("B", 2);
  Test test3("B", 3);
  Test test4("A", 4);
  Test test5("B", 5);
  // etc ...
}

PS1. notice that in the code snippet the factory pattern implementation is not shown.

PS2. Assume GCC 11.2.0 on Linux

  • 6
    I think your code has undefined behaviour as it's storing a reference to the temporary `std::string`. Why not just store the `const char*` directly or use `std::string_view` (but not references to `std::string_view` or you'll have the same problem) – Alan Birtles Jun 28 '22 at 06:18
  • 1
    "deally I would like the literals to reside on a fixed piece of memory and only the reference to this memory be given to each new object. I would like to avoid using global variables." If you have a fixed piece of memory, you might as well name it. – Quimby Jun 28 '22 at 06:26
  • 2
    *Immutable* global data is unproblematic. It's not the scope that is the problem with globals, it's the shared mutable state. If you're literally concerned about the scope, use a namespace. – molbdnilo Jun 28 '22 at 06:30
  • @AlanBirtles Thank you for pointing out that possibility. However, assuming my understanding is correct, it seems that [it is an official C++ feature to extend the lifetime of a temporary object to the lifetime of the const reference which refers to it](https://blog.galowicz.de/2016/03/23/const_reference_to_temporary_object/). – LastStarDust Jun 28 '22 at 07:02
  • 1
    In your link see `Please note that this does not apply to const reference class members, only to local const references!`. That's why I said `I think`, the rules on const reference life extension are complex so I find it simpler to just avoid it – Alan Birtles Jun 28 '22 at 07:05
  • @AlanBirtles Apparently my understanding was not correct after all! – LastStarDust Jun 28 '22 at 07:06
  • Note that you likely have small string optimization in your STL so "A" does not allocate heap space and copy "A" into it but copies "A" directly into the `std::string` object. What you can do is use `const char *` so the literals can be referenced, make your constructor constexpr and constinit all your Test objects. – Goswin von Brederlow Jun 28 '22 at 12:59

1 Answers1

0

The easiest way to get what you want is just to make name_ a const char *:

class Test {
private:
  const char *const name_;
  int value_;
public:
  Test(const char *name, int value) : name_(name), value_(value) {}
};

int main() {
  Test test1("A", 1);
  Test test2("B", 2);
  Test test3("B", 3);
  Test test4("A", 4);
  Test test5("B", 5);
  // etc ...
}

That works because the compiler will optimize strings in memory, so that in this case you don't need multiple copies of "A" in memory.

Unfortunately, the code you supply will not work, and leaves a dangling reference. The reason is that the rules for when to extend temporary object lifetimes extend when the reference is bound to a temporary materialization of an object. However, in your code, the temporary std::string is first bound to name, and then name is bound to name_. So the temporarily materialized std::string will be destroyed at the semicolon.

One downside to using a const char * is that you will lose length information in the event that you want to embed NUL characters ('\0') in your string. A potential work around would be to take the length of the string as a template argument, as in:

class Test {
private:
  const char *const name_;
  const std::size_t size_;
  int value_;
public:
  template<std::size_t N>
  Test(const char name[N], int value)
    : name_(name), size_(N-1), value_(value) {}
};
user3188445
  • 4,062
  • 16
  • 26