tonytony
tonytony

Reputation: 2023

What is the best way to return string in C++?

My question is simple: if I have some class Man and I want to define member function that returns man's name, which of the following two variants shall I prefer?

First:

string name();

Second:

void name(/* OUT */ string &name);

The first variant is kind of inefficient because it makes unnecessary copies (local variable -> return value -> variable in the left part of assignment).

The second variant looks pretty efficient but it makes me cry to write

string name;
john.name(name);

instead of simple

string name(john.name());

So, what variant shall I prefer and what is the proper trade-off between efficiency and convenience/readability?

Upvotes: 70

Views: 47036

Answers (8)

Henrik Haftmann
Henrik Haftmann

Reputation: 61

For systems without heap, i.e. small microcontrollers, it might be a good idea to return a struct containing a char array large enough for longest string.

// define a non-growable string type with adjustable size,
// similar to Turbo Pascal but (here) zero-terminated
template<unsigned N>struct String{  // see (A)
  char s[N];
};

// A function converting to binary
String<17>binaryStr(uint16_t n)   // see (B)
{
  String<17>buf;    // see (C)
  char*p=buf.s;
  uint16_t mask=0x8000;
  while (;mask!=1; mask>>=1) if (mask&n) break;
  while (;mask; mask>>=1) *p++ = mask&n ? '1' : '0';
  *p=0;
  return buf;    // see (C)
}

// Use it somewhere
printf("42 is 0b%s\n",binaryStr(42).s);  // see (D)

A: For plain C, define some strings with fixed lengths instead, as you have no templates.

B: Functions returning a struct get a hidden pointer to a caller-reserved stack space. As would be the following declaration:

String<17>& binaryStr(String<17>&, uint16_t);

The resulting string cannot have more than 16 characters plus terminating '\0', therefore 17.

C: At return, the struct has to be delivered in one piece. Accessing the hidden parameter is usually impossible. (In MSVC, it is possible using __$ReturnUdt.) Therefore, you need a local copy of the same struct and fill it with your code. Modern compilers are smart enough to avoid the "move constructor" at return statement and indeed won't allocate local stack space for buf.

D: Using the return value requires attention, especially at printf() functions having ... ellipisis (variable argument list). As standard behaviour of C/C++ is copying structures, the compiler would not complain but copying all the characters onto the stack! Therefore, you have to get a pointer, here by accessing .s. For more readability for C++ users, add

const char*c_str() const{return s;}

to the String<> class.

As net result, you have a function that really returns a string, and you neither have to cope with manual stack space reservation, nor using static char arrays, nor use heap. Gotcha, you have to pay attention to forward a pointer, not the struct, to ... parameters.

Upvotes: 0

Mahmoud Al-Qudsi
Mahmoud Al-Qudsi

Reputation: 29519

It's a good question and the fact that you're asking it shows that you're paying attention to your code. However, the good news is that in this particular case, there's an easy way out.

The first, clean method is the correct way of doing it. The compiler will eliminate unnecessary copies, in most cases (usually where it makes sense).

EDIT (6/25/2016)

Unfortunately it seems that David Abaraham's site has been offline for a few years now and that article has been lost to the ethers (no archive.org copy available). I have taken the liberty of uploading my local copy as a PDF for archival purposes, and it can be found here.

Upvotes: 56

juanchopanza
juanchopanza

Reputation: 227390

use the first variant:

string name();

The compiler will most likely optimize out any unnecessary copies. See return value optimization.

In C++11, move semantics means that you don't perform a full copy even if the compiler doesn't perform RVO. See move semantics.

Also bear in mind that if the alternative is

void name(std::string& s);

then you have to specify very clearly what can happen to s and what values it can have when passed to the function, and probably either do a lot of validity checking, or simply overwrite the input entirely.

Upvotes: 27

Mesop
Mesop

Reputation: 5263

Since you want to create a getter for a field of your class, maybe you should go like that: inline const std::string& name() const { return this->name; }

Since the name is returned as a const reference, it won't be modified outside the class, also no copy will be created by returning the name.

After that, if you want to manipulate the name you will have to do a copy.

Upvotes: 9

DevSolar
DevSolar

Reputation: 70243

Rule #1 of optimization: Measure, optimize, measure. Or, as Knuth said, "premature optimization is the root of all evil".

Unless you have a strong indication that simply returning std::string will significantly impact the performance of your software, just do so. If you can measure a significant impact, find the critical path, and optimize that. Don't make any funny, project-wide "optimizations" that likely result in little to no performance benefit, but negatively impact the readability, maintainability and robustness of your code.

Upvotes: 3

flumpb
flumpb

Reputation: 1776

I would go with the first. Return value optimization and C++11 will remove any copying overhead.

Upvotes: 6

Sarfaraz Nawaz
Sarfaraz Nawaz

Reputation: 361322

As we have move-semantics (in C++11), you can use this:

string name();

Even in C++03, this is almost good, as the compiler might optimize this (Search for Return Value Optimization).

Upvotes: 2

Robert
Robert

Reputation: 3501

I think you should use first variant. Because this is simple getter method and such getter/setter approach is used everywhere.

Upvotes: 1

Related Questions