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.