Cześć :)
Czy konstruktor w C++ jest statyczny
To jest czasem dość sporna kwestia, związana z definicją, ale ja bym powiedział że nie jest, bo pracuje na rzecz obiektu (nawet jeśli dopiero jest on w trakcie tworzenia) i ma dostęp do this
.
a w JAVIE jest?
W JAvie ma dostęp do this i może wołać metody niestatyczne, więc statyczny nie jest.
Ale nie można wywołać konstruktora na rzecz istniejącego obiektu.
Dlatego konstruktor to po prostu... konstruktor.
Zacznijmy najpierw od tego, że konstruktor musi być statyczny, bo jakby go wywołała jakaś inna funkcja? A this jest po prostu w ecx albo na stosie.
Cały myk w tym, że najpierw jest alokowana pamięć na obiekt i potem wskaźnik na tę pamięć jest przekazywany jako this do konstruktora, więc konstruktor jest metodą na niezainicjowanym obiekcie.
Statyczny konstruktor to zapewne konstruktor inicjalizujący statyczne pola. W C++ chyba czegoś takiego nie ma, bo i nie ma czegoś takiego jak ładowanie klas. Jest statyczne łączenie plików object, a więc kolejność odpalania statycznych konstruktorów zależałaby od kolejności plików object podanych do linkera. Mało rozsądne rozwiązanie.
W Javie jest coś takiego jak konstruktor statyczny, tzn wypełniający pola typu static. Jest on blokiem kodu opatrzonym słówkiem 'static'.
http://stackoverflow.com/questions/335311/static-initializer-in-java
W Javie konstruktor JEST statyczny
Taki kod:
public class Main {
public static void main(String[] args) throws Exception {
Integer i = new Integer(5);
System.out.println(i.toString());
}
}
Po przepuszczeniu przez 'javap -c' daje:
Compiled from "Main.java"
public class Main {
public Main();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]) throws java.lang.Exception;
Code:
0: new #2 // class java/lang/Integer
3: dup
4: iconst_5
5: invokespecial #3 // Method java/lang/Integer."<init>":(I)V
8: astore_1
9: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
12: aload_1
13: invokevirtual #5 // Method java/lang/Integer.toString:()Ljava/lang/String;
16: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
19: return
}
Instrukcja 'new' tworzy niezainicjowany obiekt na stercie. Instrukcja 'dup' duplikuje najwyższy element na stosie, czyli referencję do tego świeżego niezainicjowanego obiektu. Następnie jest instrukcja 'iconst_5' odkładająca na stos liczbę 5 oraz instrukcja 'invokespecial' która odpala konstruktur, pobierający jako parametry referencję do niezainicjowanego obiektu oraz jeden parametr. Na stosie zostaje referencja do poprzednio utworzonego obiektu, ale teraz jest już zainicjowany przez konstruktor. 'astore_1' zdejmuje tą referencję i wkłada ją w slot numer 1 na zmienne lokalne (ZTCP to maszyna Javy jest w części stosowa a w części rejestrowa).
tak pisze w Thinkingu in Java. Poza tym, dlaczego uważasz, ze Twój kod oznaczać miałby, że konstruktor jest statyczny/
W którym miejscu?
Może pomieszałeś zdanie. Np "jest konstruktor statyczny" zamieniłeś na "konstruktor jest statyczny"? :P
Poza tym, dlaczego uważasz, ze Twój kod oznaczać miałby, że konstruktor jest statyczny/
Bajtkod pokazuje że konstruktor obiektu jest niestatyczny, bo wymaga referencji do niezainicjowanego obiektu. Zresztą jak do tej pory nikt nie sprecyzował co to znaczy 'konstruktor statyczny', więc ciężko się kłócić o takie coś ;]
Okazuje się również, że konstruktor jest również metodą statyczną klasy...
Rozdział: Informacje o typach
Podrodział: Obiket class.
Bajtkod pokazuje że konstruktor obiektu jest niestatyczny, bo wymaga referencji do niezainicjowanego obiektu.
A dlaczego tak twierdzisz? To jeszcze o niczym nie świadczy. :)
@n0name_l:
W Javie też jest coś co można by nazwać konstruktorem statycznym: http://ideone.com/NO2CTi
A dlaczego tak twierdzisz? To jeszcze o niczym nie świadczy.
Jak wynika z bajtkodu, konstruktor wymaga jako parametru referencji do niezainicjowanego obiektu. Tak to się dzieje na najniższym poziomie (pomijając JITowanie ofc).
W C++ie konstruktor też jest mocno podobny, tzn w środku masz dostęp do thisa, więc jakiś obiekt musi być. Różnica między metodami statycznymi, a niestatycznymi to właśnie obecność thisa - konstruktor go ma, więc jest niestatyczny :]
Alokowanie niezainicjowanego obiektu jest czymś oddzielnym niż inicjowanie/ konstruowanie już zaalokowanego obiektu. Alokować można na wiele sposobów. Konstrukcja polega po prostu na przesłaniu referencji obiektu do konstruktora.
W C++ akurat jest możliwe wywołanie konstruktora na rzecz wskaźnika, czyli dowolnego kawałka pamięci. Odbywa się to za pomocą placement new: http://en.wikipedia.org/wiki/Placement_syntax
W skrócie:
new Typ(parametry)
- tutaj new
tworzy niezainicjowany obiekt i podaje go do konstruktora
new (wskaźnik) Typ(parametry)
- tutaj new
nic nie robi, jest tylko przekazanie wskaźnika (i parametrów oczywiście) do konstruktora
Zacytuję stronę wiki do której linka dał @Wibowit:
The basic problem is that the constructor is a peculiar function; when it starts off, there is no object, only raw memory. And by the time it finishes, you have a fully initialized object. Therefore i) The constructor cannot be called on an object ii) However, it needs to access (and initialize) non-static members.
Nie ma sensu szufladkowanie czy konstruktor jest statyczny czy nie. Trochę jest, a trochę nie jest.
Konstruktor to konstruktor.
Cały czas chodzi o to, że nie można stwierdzić, że konstruktor nie jest statyczny, bo bierze referencję do obiektu. Chodzi o to, że nie można tak powiedzieć, bo w tym miejscu pamięci jeszcze nie istnieje obiekt. Dopiero konstruktor biorąc referencję do tego obszaru zainicjalizuje go tworząc obiekt. Z tego punktu widzenia konstruktor nie jest metodą niestatyczną.
Dlatego ja przyjmuję za Thining in JAVA, że konstruktor jest statyczny.
Tyle, że ten niezainicjowany obiekt może wyciec na samym początku konstruktora i wtedy cała gama innych funkcji może operować na niezainicjowanym obiekcie. A skoro wg ciebie niezainicjowany obiekt to nie obiekt, to co z obiektami wyciekniętymi z konstruktora? :]
Azarien:
Trochę wyjąłeś z kontekstu. The constructor cannot be called on an object
oznacza, że nie da się napisać obiekt.Konstruktor()
. Da się natomiast odpalić konstruktor na czymkolwiek za pomocą placement new, czyli nieco innej składni do zrobienia dokładnie tego samego. Placement new można odpalić na już zainicjowanym obiekcie i to dowolnej klasy.
Tyle, że ten niezainicjowany obiekt może wyciec na samym początku konstruktora i wtedy cała gama innych funkcji może operować na niezainicjowanym obiekcie. A skoro wg ciebie niezainicjowany obiekt to nie obiekt, to co z obiektami wyciekniętymi z konstruktora? :]
W jakim sensie może wyciec? Jeżeli konstruktor byłby niestatyczne to nie może wyciec? Jeżeli też może to jaka jest odpowiedź na Twoje pytanie?
Wyciec czyli przekazać thisa do metody z innego obiektu lub wpakować do zewnętrznej kolekcji - pośrednio (przez metody własnej klasy) lub bezpośrednio (czyli prosto z konstruktora).
Chodzi np o to, że w konstruktorze mogę sobie ustawiać pole x na 5, ale przed ustawieniem np wrzucam thisa do globalnej tablicy i jakiś inny wątek dobiera się do tej referencji i wypisuje x'a. Wtedy może wypisać 0, jeżeli konstruktor nie dojdzie jeszcze do przypisania.
Jedyny nieoczywisty szczegół to moment w którym ustawiany jest wskaźnik do Vtable i inne pierdółki wspólne dla wszystkich obiektów. Czy to jest ustawiane przez konstruktor czy może przed konstruktorem, w miejscu tuż sprzed wywołania konstruktora?
I czy obecność poprawnego wskaźnika do Vtable już robi z kawałka pamięci coś co można nazwać obiektem? W C++ obiekty bez metod wirtualnych nie potrzebują i nie posiadają wskaźnika do Vtable, więc nawet ciężko odróżnić wtedy obiekt od nie-obiektu.
Oprócz tego sprawa jest oczywista: konstruktor bierze jako parametr niezainicjowany obiekt, a po zakończeniu wywołania zostawia zainicjowany obiekt.
Sama inicjalizacja równie dobrze może też być leniwa, np w obiekcie jest flaga initialized ustawiona początkowo na false (z tym, że na false w Javie ustawiać nie trzeba bo to wartość domyślna początkowa) i w każdej metodzie jest kawałek kodu typu:
if (!initialized) {
init();
initialized = true;
}
Smieszne pytanie. Konstruktor to jest tylko cukier na metode inicjalizacujaca obiekt, nic mniej i nic wiecej.
Kiedys bylo tak:
struct Foo {
};
void init(Foo* f) { ... }
Potem tak:
struct Foo {
Foo() { ... }
}
Jedyne co sie zmienilo, to to ze mozna wrzucac metody do obiektu i konstruktora nie trzeba jawnie wywolywac, ale nadal nie jest on w zadnym wypadku statyczny i nie jest on w zadnym wypadku wymagany do tego, zeby obiekt miec.
#include <iostream>
#include <cstdlib>
using namespace std;
class Foo {
private:
int a;
Foo() {}
public:
void setA(int v) { a = v; }
void printA() { cout << a << endl; }
};
int main() {
Foo* obj = (Foo*)malloc(sizeof(Foo));
obj->setA(10);
obj->printA();
free(obj);
return 0;
}
Foo::setA(int):
push rbp
mov rbp, rsp
mov QWORD PTR [rbp-8], rdi
mov DWORD PTR [rbp-12], esi
mov rax, QWORD PTR [rbp-8]
mov edx, DWORD PTR [rbp-12]
mov DWORD PTR [rax], edx
pop rbp
ret
Foo::printA():
push rbp
mov rbp, rsp
sub rsp, 16
mov QWORD PTR [rbp-8], rdi
mov rax, QWORD PTR [rbp-8]
mov eax, DWORD PTR [rax]
mov esi, eax
mov edi, OFFSET FLAT:std::cout
call std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
mov esi, OFFSET FLAT:std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)
mov rdi, rax
call std::basic_ostream<char, std::char_traits<char> >::operator<<(std::basic_ostream<char, std::char_traits<char> >& (*)(std::basic_ostream<char, std::char_traits<char> >&))
leave
ret
main:
push rbp
mov rbp, rsp
sub rsp, 16
mov edi, 4
call malloc
mov QWORD PTR [rbp-8], rax
mov rax, QWORD PTR [rbp-8]
mov esi, 10
mov rdi, rax
call Foo::setA(int)
mov rax, QWORD PTR [rbp-8]
mov rdi, rax
call Foo::printA()
mov rax, QWORD PTR [rbp-8]
mov rdi, rax
call free
mov eax, 0
leave
ret
__static_initialization_and_destruction_0(int, int):
push rbp
mov rbp, rsp
sub rsp, 16
mov DWORD PTR [rbp-4], edi
mov DWORD PTR [rbp-8], esi
cmp DWORD PTR [rbp-4], 1
jne .L5
cmp DWORD PTR [rbp-8], 65535
jne .L5
mov edi, OFFSET FLAT:std::__ioinit
call std::ios_base::Init::Init()
mov edx, OFFSET FLAT:__dso_handle
mov esi, OFFSET FLAT:std::__ioinit
mov edi, OFFSET FLAT:std::ios_base::Init::~Init()
call __cxa_atexit
.L5:
leave
ret
push rbp
mov rbp, rsp
mov esi, 65535
mov edi, 1
call __static_initialization_and_destruction_0(int, int)
pop rbp
ret