Say I have a C++ function
/**
* @param path If empty, the system default is used
*/
void foo(const std::string& path);
And in my implementation I have a default handling for empty path
s
void foo(const std::string& path) {
if (path.empty()) {
// Use some default
} else {
// Use the given path
}
}
However, the if-else blocks are quite the same so I would either write
A
void foo(const std::string& path_) {
const std::string& path = path_.empty() ? "some default" : path_;
// Use the given path
}
B
or change the function definition altogether:
void foo(std::string path) {
if (path.empty() {
path = "some default";
}
// Use the given path
}
Now I am wondering what is advised in terms of style in such cases:
- A seems a little odd at first because I explicitly made the parameter const and now start to somehow "rewrite" it. Would this code look like overkill?
- B seems a little odd because then, this function would have a different signature just because of some implementation details. It would stand out from the other functions which still use
const&
-
2Hi. This question should rather go on SO. However, you should never change the signature (i.e. interface) to satisfy some internal implementation details.Christophe– Christophe2020年01月17日 11:34:28 +00:00Commented Jan 17, 2020 at 11:34
-
@Christophe That's too absolute. Yes, you should generally prefer ease-of-use over ease-of-implementation, but choosing the right way to balance and reconcile them is an art, and includes selecting the signature and contract.Deduplicator– Deduplicator2020年01月17日 17:24:10 +00:00Commented Jan 17, 2020 at 17:24
2 Answers 2
Unless there is a good reason not to (meaning your string needs the terminator), I would prefer changing the signature:
void foo(std::string_view path)
It potentially increases efficiency for the caller and the callee.
And doing
if (path.empty())
path = "some default";
is thereafter efficient and the obvious thing to do.
Go for a good interface first, a good implementation second. Adjusting the interface can be beneficial for efficiency, but be careful not to let too many implementation-details bleed into the interface, nor to compromise usability more than warranted.
As an aside, there is a third option you didn't mention, namely tail-recursion:
void foo(const std::string& path) {
const static std::string default_path = "some_default";
if (path.empty())
return foo(defauult_path);
// Use the given path
}
-
1I understand that you're talking about rvalue optimizarion in your first example? Just be careful that you need "modern" c++ for it to works. Some unfortunate souls still work with c++98JayZ– JayZ2020年01月17日 13:50:49 +00:00Commented Jan 17, 2020 at 13:50
-
@JayZ No, the first example only needs the C++17 reference-type
std::string_view
. There are backports to at least C++11, I don't think going further down is too hard.Deduplicator– Deduplicator2020年01月18日 13:22:16 +00:00Commented Jan 18, 2020 at 13:22
Yes, it is a bit weird to change the signature of a function because of the implementation. I would prefer option A because of the mentioned reason and because it is more restrictive.
Assuming the function parameter is used to open a file at a given path, it makes a lot of sense to declare it const. But I also see no reason to use pass-by-reference using the reference symbol '&' when the parameter is already declared const. If you, for example, were to use the parameter to open a file, you just need the value. If you want to change the value of the parameter, then it makes sense, but then it should not be declared const.