W tej chwili uczę się C ++ i staram się unikać złych nawyków. Z tego, co rozumiem, clang-tidy zawiera wiele „najlepszych praktyk” i staram się ich trzymać jak najlepiej (chociaż niekoniecznie rozumiem, dlaczego są jeszcze uważane za dobre), ale nie jestem pewien, czy zrozumieć, co jest tutaj zalecane.
Użyłem tej klasy z samouczka:
class Creature
{
private:
std::string m_name;
public:
Creature(const std::string &name)
: m_name{name}
{
}
};
Prowadzi to do sugestii ze strony clang-tidy, że powinienem przekazywać wartości zamiast odniesienia i używania std::move
. Jeśli to zrobię, name
pojawia się sugestia, aby zrobić odniesienie (aby upewnić się, że nie zostanie skopiowana za każdym razem) i ostrzeżenie, std::move
które nie będzie miało żadnego efektu, ponieważ name
jest const
tak, więc powinienem je usunąć.
Jedynym sposobem, w jaki nie otrzymuję ostrzeżenia, jest const
całkowite usunięcie :
Creature(std::string name)
: m_name{std::move(name)}
{
}
Wydaje się to logiczne, ponieważ jedyną korzyścią const
było zapobieganie pomieszaniu z oryginalnym ciągiem (co nie ma miejsca, ponieważ przekazałem wartość). Ale czytałem na CPlusPlus.com :
Chociaż należy zauważyć, że -w standardowej bibliotece-przeniesienie oznacza, że obiekt przeniesiony-z pozostaje w prawidłowym, ale nieokreślonym stanie. Co oznacza, że po takiej operacji wartość przenoszonego obiektu powinna zostać jedynie zniszczona lub przypisana nowa wartość; dostęp do niego w przeciwnym razie daje nieokreśloną wartość.
Teraz wyobraź sobie ten kod:
std::string nameString("Alex");
Creature c(nameString);
Ponieważ nameString
zostanie przekazany przez wartość, std::move
unieważni tylko name
wewnątrz konstruktora i nie dotknie oryginalnego ciągu. Ale jakie są z tego zalety? Wygląda na to, że treść jest kopiowana tylko raz - jeśli przekażę przez odwołanie, gdy wywołuję m_name{name}
, jeśli przekażę wartość, gdy ją przekażę (a następnie zostanie przeniesiona). Rozumiem, że jest to lepsze niż przekazywanie wartości i nie używanie std::move
(ponieważ jest dwukrotnie kopiowane).
Więc dwa pytania:
- Czy dobrze zrozumiałem, co się tutaj dzieje?
- Czy jest jakaś korzyść z używania
std::move
przekazywania przez referencję i po prostu dzwoniącm_name{name}
?
Creature c("John");
tworzy dodatkową kopięstd::string_view
i logowanie jednokrotne.clang-tidy
to świetny sposób na obsesję na punkcie niepotrzebnych mikrooptymizacji kosztem czytelności. Pytanie, które należy tutaj zadać, przede wszystkim, brzmi: ile razy faktycznie wywołujemyCreature
konstruktora.Odpowiedzi:
Tak.
Łatwy do uchwycenia podpis funkcji bez dodatkowych przeciążeń. Podpis natychmiast ujawnia, że argument zostanie skopiowany - dzięki temu wywołujący nie zastanawiają się, czy
const std::string&
referencja może być przechowywana jako element członkowski danych, a być może później stanie się referencją wiszącą. I nie ma potrzeby przeciążania argumentówstd::string&& name
i przeciążania,const std::string&
aby uniknąć niepotrzebnych kopii, gdy rvalues są przekazywane do funkcji. Przekazywanie lwartoścido funkcji, która przyjmuje argument przez wartość, powoduje jedną kopię i jeden ruch. Przekazywanie wartości r do tej samej funkcji
powoduje dwie konstrukcje ruchu. W przeciwieństwie do tego, gdy parametr funkcji ma wartość
const std::string&
, zawsze będzie kopia, nawet w przypadku przekazania argumentu rvalue. Jest to niewątpliwie zaleta, o ile typ argumentu jest tani w konstrukcji przenoszenia (tak jest w przypadkustd::string
).Ale jest wada do rozważenia: rozumowanie nie działa dla funkcji, które przypisują argument funkcji do innej zmiennej (zamiast ją inicjować):
spowoduje cofnięcie alokacji zasobu,
m_name
do którego się odwołuje, przed ponownym przypisaniem. Polecam przeczytanie pozycji 41 w Effective Modern C ++, a także to pytanie .źródło
move
, miejsce zostanie zwolnione. Jeśli nie używammove
, zostaje zwolniony tylko wtedy, gdy przydzielone miejsce jest zbyt małe, aby pomieścić nowy ciąg, co prowadzi do poprawy wydajności. Czy to jest poprawne?m_name
zconst std::string&
parametrem, pamięć wewnętrzna jest ponownie wykorzystywane tak długo, jakm_name
pasuje. Kiedy ruch-przypisującm_name
, pamięć musi być zwalniane wcześniej. W przeciwnym razie niemożliwe było „ukraść” zasoby z prawej strony zadania.Przekazana lwartość wiąże się z
name
, a następnie jest kopiowana dom_name
.Przekazana wartość r wiąże się z
name
, a następnie jest kopiowana dom_name
.Przekazana lwartość jest kopiowana do
name
, a następnie przenoszona dom_name
.Przekazana wartość r jest przenoszona do
name
, a następnie przenoszona dom_name
.Przekazana lwartość wiąże się z
name
, a następnie jest kopiowana dom_name
.Przekazana wartość r wiąże się z
rname
, a następnie jest przenoszona dom_name
.Ponieważ operacje przenoszenia są zwykle szybsze niż kopiowanie, (1) jest lepsze niż (0), jeśli podajesz wiele tymczasowych. (2) jest optymalny pod względem kopii / ruchów, ale wymaga powtórzenia kodu.
Powtarzania kodu można uniknąć dzięki doskonałemu przekazywaniu :
Opcjonalnie możesz chcieć ograniczyć
T
, aby ograniczyć domenę typów, z których można utworzyć wystąpienie tego konstruktora (jak pokazano powyżej). C ++ 20 ma na celu uproszczenie tego za pomocą Concepts .W C ++ 17 na prvalues wpływa gwarantowana elisja kopiowania , która - w stosownych przypadkach - zmniejszy liczbę kopii / ruchów podczas przekazywania argumentów do funkcji.
źródło
Creature(const std::string &name) : m_name{std::move(name)} { }
w (2) ?To , jak mijasz, nie jest jedyną zmienną tutaj, to , co mijasz, stanowi dużą różnicę między nimi.
W C ++ mamy wszystkie rodzaje kategoriach wartości i istnieje ta „idiom” dla przypadków, gdzie przechodzą w rvalue (takiego jak
"Alex-string-literal-that-constructs-temporary-std::string"
lubstd::move(nameString)
), co skutkuje 0 egzemplarzach zstd::string
odniesienia (typ nawet nie muszą być kopiowaniem constructible dla argumentów rvalue) i używa tylkostd::string
konstruktora przenoszenia.Dość powiązane pytania i odpowiedzi .
źródło
Istnieje kilka wad podejścia pass-by-value-and-move względem odniesienia pass-by- (rv):
źródło
m_name{name}
części, w której jest kopiowany?std::string nameString("Alex"); Creature c(nameString);
jeden obiekt tonameString
, inny to argument funkcji, a trzeci to pole klasy.W moim przypadku przełączenie na przekazanie według wartości, a następnie wykonanie std: move spowodowało błąd sterty-use-after-free w programie Address Sanitizer.
https://travis-ci.org/github/acgetchell/CDT-plusplus/jobs/679520360#L3165
Więc wyłączyłem go, a także sugestię w porządku.
https://github.com/acgetchell/CDT-plusplus/compare/80c96789f0a2...0d78fd63b332
źródło