Herb hat den Sinn, dass die In-By-Wert-Zuweisung, wenn Sie bereits Speicher zugewiesen haben, ineffizient sein und eine unnötige Zuweisung verursachen kann. Das Vorbeigehen const&
ist jedoch fast so schlecht, als ob Sie eine rohe C-Zeichenfolge nehmen und an die Funktion übergeben, eine unnötige Zuordnung erfolgt.
Was Sie nehmen sollten, ist die Abstraktion des Lesens von einem String, nicht von einem String selbst, denn das ist es, was Sie brauchen.
Jetzt können Sie dies tun als template
:
class employee {
std::string name_;
public:
template<class T>
void set_name(T&& name) noexcept { name_ = std::forward<T>(name); }
};
das ist einigermaßen effizient. Dann fügen Sie vielleicht etwas SFINAE hinzu:
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); }
};
Wir bekommen also Fehler an der Schnittstelle und nicht an der Implementierung.
Dies ist nicht immer praktisch, da die Implementierung öffentlich zugänglich gemacht werden muss.
Hier string_view
kann eine Typklasse ins Spiel kommen:
template<class C>
struct string_view {
C const* b=nullptr;
C const* e=nullptr;
C const* begin() const { return b; }
C const* end() const { return e; }
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];}
string_view() = default;
string_view(string_view const&)=default;
string_view&operator=(string_view const&)=default;
string_view(C const* s, C const* f):b(s),e(f) {}
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:
static std::size_t len(C const* str) {
std::size_t r = 0;
if (!str) return r;
while (*str++) {
++r;
}
return r;
}
};
Ein solches Objekt kann direkt aus einem std::string
oder einem erstellt werden "raw C string"
und fast kostenlos speichern, was Sie wissen müssen, um daraus ein neues zu produzieren std::string
.
class employee {
std::string name_;
public:
void set_name(string_view<char> name) noexcept { name_.assign(name.begin(),name.end()); }
};
und da unsere jetzt set_name
eine feste Schnittstelle hat (keine perfekte Vorwärtsschnittstelle), kann ihre Implementierung nicht sichtbar sein.
Die einzige Ineffizienz besteht darin, dass Sie, wenn Sie einen Zeichenfolgenzeiger im C-Stil übergeben, seine Größe unnötig zweimal überschreiten (erstmaliges Suchen nach '\0'
, zweites Kopieren). Auf der anderen Seite gibt dies Ihrem Ziel Informationen darüber, wie groß es sein muss, sodass es vorab zuweisen kann, anstatt es neu zuzuweisen.
noexcept
hier ein Mythos ist, weil die Zuordnung, die auf der Seite des Angerufenen geschieht, werfen kann (z. B. beim Erstellen einesstd::string
aus rohen Zeichenfolgenliteral oder anderenstd::string
). Der Körper wirft also nicht, aber das Aufrufen dieser Funktion kann immer noch dazu führen, dass eine Ausnahme ausgelöst wird (std::bad_alloc
)