Herb has a point, in that taking by-value when you already have storage allocated within can be inefficient and cause a needless allocation. But taking by const&
is almost as bad, as if you take a raw C string and pass it to the function, a needless allocation occurs.
What you should take is the abstraction of reading from a string, not a string itself, because that is what you need.
Now, you can do this as a template
:
class employee {
std::string name_;
public:
template<class T>
void set_name(T&& name) noexcept { name_ = std::forward<T>(name); }
};
which is reasonably efficient. Then add some SFINAE maybe:
class employee {
std::string name_;
public:
template<class T>
std::enable_if_t<std::is_convertible<T,std::string>::value>
set_name(T&& name) noexcept { name_ = std::forward<T>(name); }
};
so we get errors at the interface and not the implementation.
This isn't always practical, as it requires exposing the implementation publically.
This is where a string_view
type class can come in:
template<class C>
struct string_view {
// could be private:
C const* b=nullptr;
C const* e=nullptr;
// key component:
C const* begin() const { return b; }
C const* end() const { return e; }
// extra bonus utility:
C const& front() const { return *b; }
C const& back() const { return *std::prev(e); }
std::size_t size() const { return e-b; }
bool empty() const { return b==e; }
C const& operator[](std::size_t i){return b[i];}
// these just work:
string_view() = default;
string_view(string_view const&)=default;
string_view&operator=(string_view const&)=default;
// myriad of constructors:
string_view(C const* s, C const* f):b(s),e(f) {}
// known continuous memory containers:
template<std::size_t N>
string_view(const C(&arr)[N]):string_view(arr, arr+N){}
template<std::size_t N>
string_view(std::array<C, N> const& arr):string_view(arr.data(), arr.data()+N){}
template<std::size_t N>
string_view(std::array<C const, N> const& arr):string_view(arr.data(), arr.data()+N){}
template<class... Ts>
string_view(std::basic_string<C, Ts...> const& str):string_view(str.data(), str.data()+str.size()){}
template<class... Ts>
string_view(std::vector<C, Ts...> const& vec):string_view(vec.data(), vec.data()+vec.size()){}
string_view(C const* str):string_view(str, str+len(str)) {}
private:
// helper method:
static std::size_t len(C const* str) {
std::size_t r = 0;
if (!str) return r;
while (*str++) {
++r;
}
return r;
}
};
such an object can be constructed directly from a std::string
or a "raw C string"
and nearly costlessly store what you need to know in order to produce a new std::string
from it.
class employee {
std::string name_;
public:
void set_name(string_view<char> name) noexcept { name_.assign(name.begin(),name.end()); }
};
and as now our set_name
has a fixed interface (not a perfect forward one), it can have its implementation not visible.
The only inefficiency is that if you pass in a C-style string pointer, you somewhat needlessly go over its size twice (first time looking for the '\0'
, second time copying them). On the other hand, this gives your target information about how big it has to be, so it can pre-allocate rather than re-allocate.
noexcept
is a myth here, because the allocation that happens on callee's side can throw (e.g. when building astd::string
from raw string literal or otherstd::string
). So the body does not throw, but calling that function may still result in an exception being thrown (std::bad_alloc
) – Russelrussellnoexcept
, but calling it can throw. The fact that the exception is thrown before the function body itself is entered starts to get into "how many angels can dance on the head of a pin" territory. – Eldoneldoranoexcept
really only says "If calling this function doesn't throw, I promise you won't get an exception", which is arguably less useful than it could be. – Klemperernoexcept
is more for the compiler than the users. – Edeaset_name()
? – Teacartstd::string
. That's dumb, and makes the whole exercise rather pointless. Don't make loneconst&
overloads in general. Other than the thing aboutnoexcept
, Herb's arguments for a loneconst&
should not be convincing at all for anything other thanstd::string
. – Leodora