4

I am using C++11. I am not allowed to use external libraries like boost etc. I must use STL only.

I have a number of events, which must be identified as string constants. I am not allowed to use enums or ints or any other data type. For example:

"event_name1"

"event_name2"

"some_other_event_name3"

"a_different_event_name12"

Then I have some classes which need to use these strings, but don't know the other classes exist (they don't have anything to do with each other).

class Panel{

    void postEvent(){
        SomeSingleton::postEvent("event_name");
    }
}

Another class::

class SomeClass{

    SomeClass(){
        SomeSingleton::listenForEvent("event_name");
    }

    void receiveEvent(){
         //This function is triggered when "event_name" occurs.
         //Do stuff
    }
}

All these events are constants, and are used to identify things that are happening.

Here is what I have tried:

How to store string constants that will be accessed by a number of different classes?

Some of the persons there suggested I provide specific details of how to solve a concrete problem, so I have created this new question.

How can I store the strings in a common file, so that all the other classes that use these strings can refer to the same file?

  • I do not want to waste memory or leak memory during my app's lifetime (it is a mobile app)
  • compilation times are not a big deal to me, since the project isn't so big
  • there are expected to be maybe 50 different events.
  • It seems it would be more maintainable to keep all the strings in one file, and edit only this file as and when things change.
  • Any class can listen for any event, at any time, and I won't know prior to compilation
Rahul Iyer
  • 19,924
  • 21
  • 96
  • 190

5 Answers5

8

The easiest way would be to use a char const* constant, as it's way more optimizable and don't use dynamic allocations.

Also you can use std::string_view in the postEvent function, avoiding dynamic allocations. This step is optional. If you cannot have string views and still want to avoid dynamic allocations, then refer to your implementation's SSO max capacity and keep event names below that size.

Also consider that nonstd::string_view can be shipped as a C++11 library and most likely the abstraction you need. Library such as cpp17_headers and string-view-lite exist solely for that purpose.

It look like this:

constexpr auto event_name1 = "event_name1";

In a class as a static member it works the same way:

struct Type {
    static constexpr auto event_name1 = "event_name1";
};

This will at most take space in the read-only static data of your executable.

Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141
  • 1
    OP doesn't have `string_view`. – Matthieu Brucher Apr 03 '19 at 15:28
  • The string view is of course optional. It's just to avoid dynamic allocations in each calls (if the name are really long). `char const*` also works quite well as function parameter. – Guillaume Racicot Apr 03 '19 at 15:30
  • If OP uses `const std::string&` as parameters (as he should, and probably does), you get new objects each time. At least with a static `std::string`, it's only once. – Matthieu Brucher Apr 03 '19 at 15:31
  • 1
    @MatthieuBrucher I don't think `std::string const&` is the right abstraction OP needs. It's not a silver buller. Clearly, there is no ownership of memory. Everything is static. OP simply need a reference to the static data, which is what `std::string_view` and `char const*` do. Also. backported string views are a thing. – Guillaume Racicot Apr 03 '19 at 15:34
  • Oh, I agree about `string_view`, that's why I mentioned that if he was in C++17, he should have used it. – Matthieu Brucher Apr 03 '19 at 15:36
  • I can't include any external libraries. – Rahul Iyer Apr 03 '19 at 15:40
  • 1
    @KaizerSozay then rewrite the library as internal code. String view are simple enough. or simply use `char const*` to avoid dynamic allocations in function calls. Or use `std::string` as function arguments and keep the name of the event short enough to never incur dynamic allocations. – Guillaume Racicot Apr 03 '19 at 15:42
  • Can you explain why you are using static ? When in Michael Kenzel's approach below he just uses a plain constexpr ? – Rahul Iyer Apr 03 '19 at 15:46
  • I used `static` only in the class. In namespace scope, I only used plain constexpr. The reason is that non-static datamembers cannot be constexpr, since it would not make sense (imagine an object that part of it's value is constexpr?). constexpr static data member and plain constexpr in namespace scope is equivalent. – Guillaume Racicot Apr 03 '19 at 15:49
4

In light of the fact that you're stuck with C++11, I think my suggestion from here still stands:

#ifndef INCLUDED_EVENT_NAMES
#define INCLUDED_EVENT_NAMES

#pragma once

namespace event_names
{
    constexpr auto& event_1 = "event_1";
    constexpr auto& event_2 = "event_2";
}

#endif

Defining named references to string literal objects is very simple, does not require any additional libraries, is guaranteed to not introduce any unnecessary objects, won't require any additional memory over the storage for the statically-allocated string literal objects that you'd need anyways, and will not have any runtime overhead.

If you could use C++17, I'd suggest to go with the std::string_view approach, but in C++11, I think the above is most-likely a good compromise for your application.

Michael Kenzel
  • 15,508
  • 2
  • 30
  • 39
  • There won't be multiple copies every time someone uses one of these ? Sorry, I'm a bit confused from one of the other answers in the other question. – Rahul Iyer Apr 03 '19 at 15:41
  • Also, why is everyone in this post suggesting using static ? But in the other question many people suggested just plain constexpr ? – Rahul Iyer Apr 03 '19 at 15:43
  • @KaizerSozay As explained in my original answer, string pooling is a standard optimization that any modern toolchain will perform in an optimized build (see, e.g., [here](https://stackoverflow.com/questions/53077119/can-gcc-merge-duplicate-global-string-arrays), or [here](https://learn.microsoft.com/en-us/cpp/build/reference/gf-eliminate-duplicate-strings?view=vs-2019)). If you're worried about it, you can simply verify that the string doesnt appear more than once in your binary, e.g., by simply opening the file and searching for how many times each string appears… – Michael Kenzel Apr 03 '19 at 15:59
  • That's interesting. Thanks for taking the time to explain all this. I'm learning a lot of stuff I didn't know. – Rahul Iyer Apr 03 '19 at 16:01
3

Global const std::string has one drawback it need processing during startup and creates copy of string literal.

The linked SO answear uses constexpr std::string_view and this is cool solution since constructor is constexpr so nothing have to be done on startup. Also it doesn't create any copy. Problem is that this is C++17

Use of const char [] (or auto or constexpr) is old proven solution. You can compare std::string with it without any extra overhead.

You can create header file for all that strings and let linker to remove all duplicates. It was working like that in old C++.

Marek R
  • 32,568
  • 6
  • 55
  • 140
2

You can have a struct of static strings:

struct MyNames
{
    static const std::string name1;
};

And in a cpp:

const std::string MyNames::name1 = "foo";

You can then access the names from all your required locations. In C++17, you would have used string_view instead to avoid object construction. But this seems to be a duplicate of this answer, basically: https://stackoverflow.com/a/55493109/2266772

Matthieu Brucher
  • 21,634
  • 7
  • 38
  • 62
  • 1
    `s/const/constexpr/` ? – Jesper Juhl Apr 03 '19 at 15:26
  • Isn't memory allocation not working for `constexpr`? – Matthieu Brucher Apr 03 '19 at 15:27
  • Haha yes, that's the question I created and mentioned in my question description. – Rahul Iyer Apr 03 '19 at 15:29
  • Many people have criticised using a struct or a class, and suggested using a namespace. They've also shunned using static and suggested using extern instead. I'm afraid I'm still confused – Rahul Iyer Apr 03 '19 at 15:31
  • 1
    struct or namespace, that's a matter of taste. The `extern` is just a declaration, requires the namespace, which is the point of the struct, you declare and then implement in a cpp. – Matthieu Brucher Apr 03 '19 at 15:33
  • I thought extern is specifically used for members that are global to any file, but static is global only to the same file or possibly function? – Rahul Iyer Apr 03 '19 at 15:37
  • Does this mean that at runtime, regardless of which solution I use extern / static etc, there is always only just one copy of the string in memory, and it is created just once ? – Rahul Iyer Apr 03 '19 at 15:39
  • 1
    Yes, there is only one version, as you have only one implementation of `name`. For the header versions, it's the task of the linker to remove the duplicates. – Matthieu Brucher Apr 03 '19 at 15:52
1

For the sake of proper abstraction and good design, you should define an event class. This event class will have either:

  • A method which provide a string (e.g. name() or system_name())
  • A conversion operator to a string (not recommended)
  • A to_string() freestanding function which takes such an event (not recommend)

But beyond that - all of your class can now use an enum, or an index, or whatever they like - they'll just need to use the conversion method whenever they interact with whatever it is that requires strings. Thus none of your classes has to actually know about those strings itself.

The strings themselves can stay within the .cpp implementation file of the class, and nobody else has to know about them. (unles they are actually defined in code that's not yours, but that's not how you described the problem.)

einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • That makes a lot of sense. I was just thinking about it when I saw your post. – Rahul Iyer Apr 03 '19 at 15:48
  • But then how would I store the string names in that class ? Just as private string constants ? – Rahul Iyer Apr 03 '19 at 15:50
  • You mentioned that the strings can stay within the cpp implementation file - do you mean inside a function ? But won't I be creating them every invocation then ? Why wouldn't I just keep them all as private constants in the header ? – Rahul Iyer Apr 03 '19 at 15:59
  • @KaizerSozay: They can be static - inside or outside of a function, doesn't matter all that much. – einpoklum Apr 03 '19 at 16:11
  • This seems to make the most sense to me design wise but I think the overhead of comparing all the strings would be too much. Worst case you would have to compare one string with all the other possibilities. In the case of using static strings like the other answers this problem doesn’t exist. – Rahul Iyer Apr 03 '19 at 16:18