How to cleanly use: const char* and std::string?

2019-04-09 10:29发布

tl:dr

How can I concatenate const char* with std::string, neatly and elegantly, without multiple function calls. Ideally in one function call and have the output be a const char*. Is this impossible, what is an optimum solution?

Initial Problem

The biggest barrier I have experienced with C++ so far is how it handles strings. In my opinion, of all the widely used languages, it handles strings the most poorly. I've seen other questions similar to this that either have an answer saying "use std::string" or simply point out that one of the options is going to be best for your situation.

However this is useless advice when trying to use strings dynamically like how they are used in other languages. I cannot guaranty to always be able to use std::string and for the times when I have to use const char* I hit the obvious wall of "it's constant, you can't concatenate it".

Every solution to any string manipulation problem I've seen in C++ requires repetitive multiple lines of code that only work well for that format of string. I want to be able to concatenate any set of characters with the + symbol or make use of a simple format() function just how I can in C# or Python. Why is there no easy option?

Current Situation

Standard Output

I'm writing a DLL and so far I've been output text to cout via the << operator. Everything has been going fine so far using simple char arrays in the form:

cout << "Hello world!"

Runtime Strings

Now it comes to the point where I want to construct a string at runtime and store it with a class, this class will hold a string that reports on some errors so that they can be picked up by other classes and maybe sent to cout later, the string will be set by the function SetReport(const char* report). So I really don't want to use more than one line for this so I go ahead and write something like:

SetReport("Failure in " + __FUNCTION__ + ": foobar was " + foobar + "\n"); // __FUNCTION__ gets the name of the current function, foobar is some variable

Immediately of course I get:

  • expression must have integral or unscoped enum type and...
  • '+': cannot add two pointers

Ugly Strings

Right. So I'm trying to add two or more const char*s together and this just isn't an option. So I find that the main suggestion here is to use std::string, sort of weird that typing "Hello world!" doesn't just give you one of those in the first place but let's give it a go:

SetReport(std::string("Failure in ") + std::string(__FUNCTION__) + std::string(": foobar was ") + std::to_string(foobar) + std::string("\n"));

Brilliant! It works! But look how ugly that is!! That's some of the ugliest code I've every seen. We can simplify to this:

SetReport(std::string("Failure in ") + __FUNCTION__ + ": foobar was " + std::to_string(foobar) + "\n");

Still possibly the worst way I've every encounter of getting to a simple one line string concatenation but everything should be fine now right?

Convert Back To Constant

Well no, if you're working on a DLL, something that I tend to do a lot because I like to unit test so I need my C++ code to be imported by the unit test library, you will find that when you try to set that report string to a member variable of a class as a std::string the compiler throws a warning saying:

warning C4251: class 'std::basic_string<_Elem,_Traits,_Alloc>' needs to have dll-interface to be used by clients of class'

The only real solution to this problem that I've found other than "ignore the warning"(bad practice!) is to use const char* for the member variable rather than std::string but this is not really a solution, because now you have to convert your ugly concatenated (but dynamic) string back to the const char array you need. But you can't just tag .c_str() on the end (even though why would you want to because this concatenation is becoming more ridiculous by the second?) you have to make sure that std::string doesn't clean up your newly constructed string and leave you with garbage. So you have to do this inside the function that receives the string:

const std::string constString = (input);
m_constChar = constString.c_str();

Which is insane. Because now I traipsed across several different types of string, made my code ugly, added more lines than should need and all just to stick some characters together. Why is this so hard?

Solution?

So what's the solution? I feel that I should be able to make a function that concatenates const char*s together but also handle other object types such as std::string, int or double, I feel strongly that this should be capable in one line, and yet I'm unable to find any examples of it being achieved. Should I be working with char* rather than the constant variant, even though I've read that you should never change the value of char* so how would this help?

Are there any experienced C++ programmers who have resolved this issue and are now comfortable with C++ strings, what is your solution? Is there no solution? Is it impossible?

4条回答
贪生不怕死
2楼-- · 2019-04-09 10:43

One of the simplest solution is to use an C++ empty string. Here I declare empty string variable named _ and used it in front of string concatenation. Make sure you always put it in the front.

#include <cstdio>
#include <string>

using namespace std;
string _ = "";

int main() {
        char s[] = "chararray";
        string result =
                _ + "function name = [" + __FUNCTION__ + "] "
                "and s is [" + s + "]\n";
        printf( "%s", result.c_str() );
        return 0;
}

Output:

function name = [main] and s is [chararray]

Regarding __FUNCTION__, I found that in Visual C++ it is a macro while in GCC it is a variable, so SetReport("Failure in " __FUNCTION__ "; foobar was " + foobar + "\n"); will only work on Visual C++. See: https://msdn.microsoft.com/en-us/library/b0084kay.aspx and https://gcc.gnu.org/onlinedocs/gcc/Function-Names.html

The solution using empty string variable above should work on both Visual C++ and GCC.

查看更多
淡お忘
3楼-- · 2019-04-09 10:53

My Solution

I've continued to experiment with different things and I've got a solution which combines tivn's answer that involves making an empty string to help concatenate long std::string and character arrays together and a function of my own which allows single line copying of that std::string to a const char* which is safe to use when the string object leaves scope.

I would have used Mike Seymour's variadic templates but they don't seem to be supported by the Visual Studio 2012 I'm running and I need this solution to be very general so I can't rely on them.

Here is my solution:

Strings.h

#ifndef _STRINGS_H_
#define _STRINGS_H_

#include <string>

// tivn's empty string in the header file
extern const std::string _;

// My own version of .c_str() which produces a copy of the contents of the string input
const char* ToCString(std::string input);

#endif

Strings.cpp

#include "Strings.h"

const std::string str = "";

const char* ToCString(std::string input)
{
    char* result = new char[input.length()+1];
    strcpy_s(result, input.length()+1, input.c_str());
    return result;
}

Usage

m_someMemberConstChar = ToCString(_ + "Hello, world! " + someDynamicValue);

I think this is pretty neat and works in most cases. Thank you everyone for helping me with this.

查看更多
在下西门庆
4楼-- · 2019-04-09 10:56

Plain string literals (e.g. "abc" and __FUNCTION__) and char const* do not support concatenation. These are just plain C-style char const[] and char const*.

Solutions are to use some string formatting facilities or libraries, such as:

  • std::string and concatenation using +. May involve too many unnecessary allocations, unless operator+ employs expression templates.
  • std::snprintf. This one does not allocate buffers for you and not type safe, so people end up creating wrappers for it.
  • std::stringstream. Ubiquitous and standard but its syntax is at best awkward.
  • boost::format. Type safe but reportedly slow.
  • cppformat. Reportedly modern and fast.
查看更多
贼婆χ
5楼-- · 2019-04-09 11:09

The standard way to build a string, formatting non-string types as strings, is a string stream

#include <sstream>

std::ostringstream ss;
ss << "Failure in " << __FUNCTION__ << ": foobar was " << foobar << "\n";
SetReport(ss.str());

If you do this often, you could write a variadic template to do that:

template <typename... Ts> std::string str(Ts&&...);
SetReport(str("Failure in ", __FUNCTION__, ": foobar was ", foobar, '\n'));

The implementation is left as an exercise for the reader.

In this particular case, string literals (including __FUNCTION__) can be concatenated by simply writing one after the other; and, assuming foobar is a std::string, that can be concatenated with string literals using +:

SetReport("Failure in " __FUNCTION__ ": foobar was " + foobar + "\n");

If foobar is a numeric type, you could use std::to_string(foobar) to convert it.

查看更多
登录 后发表回答