Home > Net >  Does it make sense to use a reference in this case?
Does it make sense to use a reference in this case?

Time:07-22

I wonder if it makes sense to use references when using literal constants. Below i made a few examples:

bool funcWithoutReference(const std::string Text)
{
    return Text == "Hello!";
}

bool funcWithReference(const std::string& Text)
{
    return Text == "Hello!";
}

int main()
{
    // Example 1: Average execution time: 13.652 sec
    for (int i = 0; i < 10000000; i  ) {
        std::string text = "Hello";
        funcWithoutReference(text);
    }

    // Example 2: Average execution time: 7.283 sec
    for (int i = 0; i < 10000000; i  ) {
        funcWithoutReference("Hello");
    }

    // Example 3: Average execution time: 7.249 sec
    for (int i = 0; i < 10000000; i  ) {
        std::string text = "Hello";
        funcWithReference(text);
    }

    // Example 4: Average execution time: 7.292 sec
    for (int i = 0; i < 10000000; i  ) {
        funcWithReference("Hello");
    }

    return 0;
}

Why is there such a difference between examples 1 and 2? I understand that using a literal constant in memory a "temporary" variable is created and passed as a parameter to the function. But why does example 2 get results almost like 3 and 4? Is a copy only made when we pass a variable as a parameter?

Time was measured as standard, using clock_t. Thanks for your help.

CodePudding user response:

Examples 2, 3, and 4 are identical. In each, you create a single std::string per loop iteration. Example 1 does this too, but also copies it.

Let's break it down...

Example 1

Total string objects: 2

std::string text = "Hello";
funcWithoutReference(text);

Every time around the loop, you create a new string, initialized from the literal "Hello". You then pass it by value, so it is copied as a result of the function call.

Example 2

Total string objects: 1

funcWithoutReference("Hello");

Every time around the loop, you create a temporary string, initialized from the literal "Hello". This happens as a result of resolving the function call that will implicitly convert the literal to a std::string by way of one of its available constructors. This string is an rvalue, meaning a temporary result. It only exists for the duration of the function call.

Example 3

Total string objects: 1

std::string text = "Hello";
funcWithReference(text);

Every time around the loop, you create a new string, initialized from the literal "Hello". You then pass this by reference, so no additional copies are made.

Example 4

Total string objects: 1

funcWithReference("Hello");

Every time around the loop, you create a temporary string, initialized from the literal "Hello". This happens as a result of resolving the function call that requires a const std::string&. To achieve that, the literal is first implicitly converted to a std::string rvalue by way of one of its available constructors. That is then provided by reference.

Notes

It should be pointed out that your test might be a bit bogus. These loops have no actual side-effects, and could potentially be optimized away completely. And Example 1 could also be optimized to elide the copy because there is no other use or side-effect for the local string.

Improvement

To avoid the overhead of allocating a new std::string, you may choose to use std::string_view (since C 17). This is a light-weight string-like interface that works on both literals and std::string. It does not do any dynamic allocation.

The cost of copying a string_view is therefore very low so you can pass them by value or reference without incurring a significant difference in computational cost.

#include <string_view>

bool funcWithoutReference(std::string_view Text)
{
    return Text == "Hello!";
}

bool funcWithReference(const std::string_view& Text)
{
    return Text == "Hello!";
}

int main()
{
    // No allocation per iteration
    for (int i = 0; i < 10000000; i  ) {
        funcWithoutReference("Hello");
    }

    for (int i = 0; i < 10000000; i  ) {
        funcWithReference("Hello");
    }

    // Single allocation per iteration
    for (int i = 0; i < 10000000; i  ) {
        funcWithoutReference(std::string{"Hello"});
    }

    // Single allocation per iteration
    for (int i = 0; i < 10000000; i  ) {
        funcWithReference(std::string{"Hello"});
    }
}

  •  Tags:  
  • c
  • Related