Czy istnieje konkretny powód, dla którego złamałoby to język koncepcyjnie, czy konkretny powód, dla którego jest to technicznie niewykonalne w niektórych przypadkach?
Zastosowanie byłoby z nowym operatorem.
Edycja: Porzucę nadzieję na wyprostowanie mojego „nowego operatora” i „nowego operatora” i będę bezpośredni.
Pytanie brzmi: dlaczego konstruktorzy są wyjątkowi ? Pamiętaj oczywiście, że specyfikacje językowe mówią nam, co jest legalne, ale niekoniecznie moralne. To, co jest legalne, zwykle jest informowane przez to, co logicznie spójne z resztą języka, co jest proste i zwięzłe oraz co jest możliwe do wdrożenia przez kompilatory. Ewentualne uzasadnienie komitetu norm przy ważeniu tych czynników jest celowe i interesujące - stąd pytanie.
std::make_unique
istd::make_shared
może odpowiednio rozwiązać praktyczną motywację leżącą u podstaw tego pytania. Są to metody szablonów, co oznacza, że należy przechwycić argumenty wejściowe do konstruktora, a następnie przesłać je do rzeczywistego konstruktora.Odpowiedzi:
Funkcje wskaźników do członka mają sens tylko wtedy, gdy masz więcej niż jedną funkcję członka z tą samą sygnaturą - w przeciwnym razie wskaźnik byłby tylko jedną możliwą wartością. Nie jest to jednak możliwe w przypadku konstruktorów, ponieważ w C ++ różne konstruktory tej samej klasy muszą mieć różne podpisy.
Alternatywą dla Stroustrup byłoby wybranie składni dla C ++, w której konstruktory mogłyby mieć inną nazwę niż nazwa klasy - ale to zapobiegłoby niektórym bardzo eleganckim aspektom istniejącej składni ctor i uczyniłoby język bardziej skomplikowanym. Dla mnie wygląda to na wysoką cenę, aby pozwolić na rzadko potrzebną funkcję, którą można łatwo zasymulować poprzez „outsourcing” inicjalizacji obiektu z ctor do innej
init
funkcji (normalna funkcja składowa, dla której można wskazać wskaźnik do elementów) Utworzony).źródło
memcpy(buffer, (&std::string)(int, char), size)
? (Prawdopodobnie wyjątkowo nie koszerny, ale w końcu jest to C ++.)memcpy(buffer, strlen, size)
. Prawdopodobnie skopiowałoby to zgromadzenie, ale kto wie. Niezależnie od tego, czy kod mógłby zostać wywołany bez awarii, wymagałaby wiedzy o używanym kompilatorze. To samo dotyczy określania rozmiaru. Byłoby to w dużej mierze zależne od platformy, ale w kodzie produkcyjnym używanych jest wiele nieprzenośnych konstrukcji C ++. Nie widzę powodu, by to zakazać.Konstruktor to funkcja wywoływana, gdy obiekt jeszcze nie istnieje, więc nie może być funkcją składową. Może być statyczny.
Konstruktor faktycznie zostaje wywołany za pomocą tego wskaźnika, po przydzieleniu pamięci, ale przed całkowitą inicjalizacją. W konsekwencji konstruktor ma wiele uprzywilejowanych funkcji.
Gdybyś miał wskaźnik do konstruktora, musiałby to być wskaźnik statyczny, coś w rodzaju funkcji fabrycznej lub specjalny wskaźnik do czegoś, co można nazwać natychmiast po przydzieleniu pamięci. Nie może to być zwykła funkcja składowa i nadal działać jako konstruktor.
Jedynym użytecznym celem, jaki przychodzi mi do głowy, jest specjalny rodzaj wskaźnika, który można przekazać nowemu operatorowi, aby umożliwić mu pośredni wybór konstruktora. Myślę, że to może się przydać, ale wymagałoby to znacznej nowej składni i prawdopodobnie odpowiedź brzmi: pomyśleli o tym i nie było warte wysiłku.
Jeśli chcesz po prostu refaktoryzować wspólny kod inicjujący, zwykła funkcja pamięci jest zwykle wystarczającą odpowiedzią i możesz uzyskać wskaźnik do jednego z nich.
źródło
&A::A
nie działa w żadnym kompilatorze, którego próbowałem).Wynika to z faktu, że nie jest to konstruktor typu zwracanego i nie rezerwujesz w pamięci miejsca dla konstruktora. Tak jak robisz w przypadku zmiennej podczas deklaracji. Na przykład: jeśli napiszesz prostą zmienną X, wówczas kompilator wygeneruje błąd, ponieważ kompilator nie zrozumie jego znaczenia. Ale kiedy piszesz Int x; Następnie kompilator dowiaduje się, że jest zmienną typu int, dlatego zarezerwuje miejsce dla zmiennej.
Wniosek: - więc wniosek jest taki, że z powodu wykluczenia typu zwrotu nie zapisze adresu w pamięci.
źródło
(void)(*fptr)()
deklaruje wskaźnik do funkcji bez wartości zwracanej.Odgadnę dziko:
Konstruktor i destruktor C ++ wcale nie są funkcjami: są makrami. Wchodzą one w zakres, w którym obiekt jest tworzony, i zakres, w którym obiekt jest niszczony. Z kolei nie ma konstruktora ani destruktora, obiekt po prostu JEST.
Właściwie myślę, że inne funkcje w klasie nie są również funkcjami, ale funkcjami wbudowanymi, które NIE są wprowadzane, ponieważ przyjmujesz ich adres (kompilator zdaje sobie sprawę, że na nim jesteś, nie wstawia ani nie wstawia kodu do funkcji i optymalizuje tę funkcję), a z kolei funkcja wydaje się „nadal tam być”, nawet jeśli nie zrobiłbyś tego, gdybyś jej nie wziął pod uwagę.
Wirtualna tabela „obiektu” C ++ nie jest jak obiekt JavaScript, w którym można uzyskać jego konstruktor i tworzyć z niego obiekty w czasie wykonywania
new XMLHttpRequest.constructor
, ale raczej zbiór wskaźników do anonimowych funkcji, które działają jak środek do komunikacji z tym obiektem , z wyłączeniem możliwości utworzenia obiektu. I nawet nie ma sensu „usuwać” obiektu, ponieważ jest to jak próba usunięcia struktury, nie możesz: to tylko etykieta stosu, po prostu napisz do niego, jak chcesz, pod inną etykietą: możesz użyj klasy jako 4 liczb całkowitych:Nie ma przecieku pamięci, nie ma problemów, oprócz tego, że skutecznie zmarnowałeś garść miejsca na stosie, które jest zarezerwowane dla interfejsu obiektów i łańcucha, ale nie zniszczy twojego programu (dopóki nie spróbujesz go użyć jako ciąg ponownie).
W rzeczywistości, jeśli moje wcześniejsze założenia są poprawne: całkowity koszt łańcucha to tylko koszt przechowywania tych 32 bajtów i stałej przestrzeni łańcucha: funkcje są używane tylko w czasie kompilacji, a po zakończeniu mogą być również wstawiane i odrzucane obiekt jest tworzony i używany (tak jakbyś pracował ze strukturą i odwoływał się do niej bezpośrednio, bez żadnych wywołań funkcji, to na pewno są powielone wywołania zamiast skoków funkcji, ale zwykle jest to szybsze i zajmuje mniej miejsca). W istocie, za każdym razem, gdy wywołujesz dowolną funkcję, kompilator po prostu zastępuje to wywołanie instrukcjami, aby dosłownie to zrobić, z wyjątkami ustawionymi przez projektantów języka.
Podsumowanie: obiekty C ++ nie mają pojęcia, czym są; wszystkie narzędzia do łączenia się z nimi są wbudowane statycznie i utracone w czasie wykonywania. To sprawia, że praca z klasami jest tak wydajna jak wypełnianie struktur danymi i bezpośrednia praca z tymi danymi w ogóle bez wywoływania jakichkolwiek funkcji (funkcje te są wbudowane).
Jest to całkowicie odmienne od podejść COM / ObjectiveC, a także javascript, które dynamicznie przechowują informacje o typie, kosztem narzutu, zarządzania pamięcią, wywołań konstrukcji, ponieważ kompilator nie może wyrzucić tych informacji: jest to konieczne do wysyłki dynamicznej. To z kolei daje nam możliwość „rozmawiania” z naszym programem w czasie wykonywania i rozwijania go podczas działania, dzięki posiadaniu elementów odblaskowych.
źródło
struct { int size; const char * data; };
(jak się wydaje, że zakładasz), zapisujesz 4 * 4 bajty = 16 bajtów na adres pamięci, w którym zarezerwowałeś tylko 8 bajtów na maszynie x86, więc 8 bajtów jest zapisywanych na innych danych (co może uszkodzić twój stos ). Na szczęściestd::string
normalnie ma pewną optymalizację w miejscu dla krótkich ciągów, więc powinna być wystarczająco duża dla twojego przykładu, gdy używasz dużej implementacji standardowej.