Konstruktor przenoszący
Część sekcji Programowanie obiektowe ścieżki C++ w Coddy. Lekcja 21 z 104.
Konstruktor przenoszący przenosi zasoby z obiektu tymczasowego zamiast je kopiować. Podczas gdy konstruktor kopiujący duplikuje dane, konstruktor przenoszący „przejmuje” zasoby, pozostawiając obiekt źródłowy w prawidłowym, ale pustym stanie. Pozwala to uniknąć kosztownych kopii głębokich, gdy obiekt źródłowy i tak ma zostać zniszczony.
Konstruktor przenoszący przyjmuje referencję do r-wartości, oznaczaną przez &&:
class Buffer {
int* data;
size_t size;
public:
Buffer(size_t s) : size(s), data(new int[s]) {}
// Konstruktor przenoszący
Buffer(Buffer&& other) noexcept {
data = other.data; // Przejmij wskaźnik
size = other.size;
other.data = nullptr; // Pozostaw obiekt źródłowy w poprawnym stanie
other.size = 0;
}
~Buffer() { delete[] data; }
};Kluczowa różnica w porównaniu z kopiowaniem: nie przydzielamy nowej pamięci. Po prostu przejmujemy własność istniejącej pamięci i ustawiamy wskaźnik źródła na nullptr, aby jego destruktor nie usunął naszych danych.
Konstruktory przenoszące są wywoływane podczas inicjalizacji z obiektów tymczasowych lub przy użyciu std::move():
Buffer createBuffer() {
return Buffer(1000); // Użyto konstruktora przenoszącego (wartość zwracana)
}
Buffer b1(1000);
Buffer b2(std::move(b1)); // Jawne przeniesienie – b1 jest teraz pustyOznaczaj konstruktory przenoszące jako noexcept, gdy to możliwe. Informuje to kompilator, że operacja nie zgłosi wyjątków, co umożliwia ważne optymalizacje w kontenerach takich jak std::vector.
Wyzwanie
ŁatwyZbudujmy system pakietów danych, który pokaże, jak konstruktory przenoszące sprawnie przekazują własność zasobów. Utworzysz klasę DataPacket, która zarządza dynamicznie przydzielaną tablicą bajtów, i zaimplementujesz konstruktor przenoszący, który „przejmuje” dane zamiast je kopiować.
Utworzysz dwa pliki, aby uporządkować kod:
DataPacket.h: Zdefiniuj klasęDataPacket, która zarządza ładunkiem danych typu całkowitego. Twoja klasa powinna mieć:- Prywatne składowe: wskaźnik do tablicy liczb całkowitych (
payload),sizeokreślający liczbę elementów orazpacketId(ciąg znaków) identyfikujący pakiet - Konstruktor sparametryzowany, który przyjmuje identyfikator pakietu i rozmiar, przydziela pamięć dla tablicy i wypełnia ją wartościami od 0 do size-1. Wyświetl
"Packet <id> created with size <size>" - Konstruktor przenoszący oznaczony jako
noexcept, który przenosi własność ładunku z obiektu źródłowego. Wyświetl"Packet <id> moved". Pamiętaj, aby pozostawić obiekt źródłowy w prawidłowym, pustym stanie (nullptr, size 0) - Destruktor, który zwalnia pamięć, jeśli wskaźnik nie jest pusty, i wyświetla
"Packet <id> destroyed" - Metodę
getSize(), która zwraca bieżący rozmiar - Metodę
getId(), która zwraca identyfikator pakietu - Metodę
getSum(), która zwraca sumę wszystkich elementów ładunku (zwraca 0, jeśli payload jest pusty)
- Prywatne składowe: wskaźnik do tablicy liczb całkowitych (
main.cpp: Pokaż działanie konstruktora przenoszącego, odczytując z wejścia identyfikator pakietu i jego rozmiar. Następnie:- Utwórz obiekt
DataPacketo nazwieoriginalz wprowadzonymi wartościami - Wyświetl
"Original - ID: <id>, Size: <size>, Sum: <sum>" - Utwórz nowy pakiet o nazwie
transferred, przenosząc dane zoriginalza pomocąstd::move() - Wyświetl
"After move:" - Wyświetl
"Original - ID: <id>, Size: <size>, Sum: <sum>" - Wyświetl
"Transferred - ID: <id>, Size: <size>, Sum: <sum>"
- Utwórz obiekt
Format danych wejściowych:
- Pierwszy wiersz: identyfikator pakietu (ciąg znaków)
- Drugi wiersz: rozmiar (liczba całkowita)
Po przeniesieniu oryginalny pakiet powinien mieć rozmiar 0 i sumę 0 (ponieważ jego dane zostały przeniesione), natomiast przeniesiony pakiet powinien zawierać wszystkie oryginalne dane. To pokazuje, jak semantyka przenoszenia pozwala uniknąć kosztownych głębokich kopii przez samo przeniesienie własności wskaźnika.
Dołącz plik nagłówkowy w main.cpp za pomocą #include "DataPacket.h" i nie zapomnij dołączyć <utility>, aby użyć std::move().
Spróbuj swoich sił
#include <iostream>
#include <string>
#include <utility>
#include "DataPacket.h"
using namespace std;
int main() {
// Wczytaj dane wejściowe
string packetId;
int size;
cin >> packetId;
cin >> size;
// TODO: Utwórz obiekt DataPacket o nazwie 'original' z wartościami wejściowymi
// TODO: Wypisz "Original - ID: <id>, Size: <size>, Sum: <sum>"
// TODO: Utwórz nowy pakiet o nazwie 'transferred', przenosząc dane z original
// Wskazówka: Użyj std::move()
// TODO: Wypisz "After move:"
// TODO: Wypisz "Original - ID: <id>, Size: <size>, Sum: <sum>"
// TODO: Wypisz "Transferred - ID: <id>, Size: <size>, Sum: <sum>"
return 0;
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy programowania obiektowego
Pliki zewnętrzneBudowanie i kompilacja C++Pliki nagłówkowe i pliki źródłowePrzestrzenie nazw i zakresWprowadzenie do programowania obiektowego w C++Klasy a obiektyWskaźnik „this”Metody (funkcje składowe)Atrybuty (składowe danych)Podstawy konstruktorów i destruktorówPowtórzenie – prosty kalkulator4Właściwości klas
Elementy instancji a elementy statyczneGettery i setteryStałe funkcje składoweSłowo kluczowe mutableMetody i zmienne statyczneFunkcje i klasy zaprzyjaźnionePodsumowanie – menedżer konta bankowego7Dziedziczenie
Podstawy dziedziczeniaPoziomy dostępu w dziedziczeniuKolejność wywołań konstruktora i destruktoraNadpisywanie metodFunkcje wirtualne i VTableWielokrotne dziedziczenieDziedziczenie wirtualnePowtórzenie — hierarchia pracowników2Zarządzanie pamięcią
Pamięć stosu a stertyWskaźniki i referencjePamięć dynamiczna (new/delete)Inteligentne wskaźniki w C++RAII w C++Podsumowanie — menedżer tablicy dynamicznej5Hermetyzacja
Specyfikatory dostępu w C++Specyfikatory dostępu — szczegółowoUkrywanie informacjiStruktura a klasaKlasy zagnieżdżone i wewnętrznePodsumowanie — system ewidencji studentów8Polimorfizm
Polimorfizm czasu kompilacji i wykonaniaPrzeciążanie funkcjiFunkcje wirtualne — powtórkaCzysto wirtualne funkcjeKlasy abstrakcyjneProjektowanie interfejsów w C++Rzutowanie dynamiczne i RTTIPodsumowanie — kalkulator kształtów3Konstruktory i destruktory
Konstruktor domyślnyKonstruktor z parametramiKonstruktor kopiującyKonstruktor przenoszącyListy inicjalizacyjne konstruktoraKonstruktory delegująceDestruktor — szczegółowe omówienieZasada trzech / pięciu / zeraPodsumowanie — klasa String6Przeciążanie operatorów
Wprowadzenie do przeciążania operatorówPrzeciążanie operatorów arytmetycznychPrzeciążanie operatorów porównaniaOperatory strumieniowePrzeciążanie operatora przypisaniaPrzeciążanie operatorów [] i ()Operatory konwersji typówPodsumowanie — klasa Matrix9Szablony
Szablony funkcjiSzablony klasSpecjalizacja szablonówSzablony wariadycznePodstawy SFINAE i cech typówPodsumowanie — kontener generycznyPoćwicz samodzielnie: Kompilator C++ online