Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?

Wywołanie konstruktora

Ostatnio zmodyfikowano 2014-12-28 15:10
Autor Wiadomość
Malakian
Temat założony przez niniejszego użytkownika
Wywołanie konstruktora
» 2014-12-24 11:20:17
Wiam

Mam pytanie, mianowicie dlaczego jest możliwe takie wywołanie konstruktora

C/C++
string( "Text hoes here" );

// ale już takie powoduje błąd?

string::string( "Text goes here" );


Pytam, ponieważ to drugie wydaje się bardziej logiczne. Można by pomyśleć, że nie isnieje zdefiniowana funkcja string(), natomiast w przestrzeni nazw klasy string już taka instnieje (konstruktor).
P-123297
Kaikso
» 2014-12-24 18:06:44
Bo nie istnieje taka.

Konstruktor jest w uproszczeniu taką specjalną metodą (bez nazwy, używa się ją jedynie do deklaracji) która jest wywoływana automatycznie lub poprzez specyficzną składnie.
1.) Dzięki operatorowi zakresu `::' można się dostać do metod i pól statycznych klasy, a konstruktor nie jest statyczny.
2.) W takiej postaci niemożliwe było by wywoływanie jawnie konstruktorów dla typów wbudowanych np.:
C/C++
//Prawdziwa składnia
int a( 1 );
int * b = new int( a );
float c = float( * b );

//Ta druga (błędna) składnia
int a::int( 1 );
int * b = new int::int( a );
float c = float::float( * b );
Czy to było by bardziej z sensem?
3.) Konstruktor nie zwraca wartości, a jedynie wypełnia objekt (i to on jest odbierany w kodzie w postaci zdefiniowanej lub tymczasowej).
4.) Rdzeń języka C i C++ jest zatwierdzany przez komisje, więc raczej musi to mieć sens bo jak na razie to jedynie programiści zawodzą, a nie język.

Tego było by jeszcze dużo ale nie ma sensu tego wymieniać :P
P-123305
darko202
» 2014-12-25 11:05:24
Nie do końca masz rację Kaikso, bo pytanie/konkluzja brzmiała  
>> Można by pomyśleć, że nie istnieje zdefiniowana funkcja string(), natomiast w przestrzeni nazw klasy string już taka istnieje (konstruktor).
Ty zaś odpowiadasz na trochę inne pytanie. tzn. sens istnienia ::

Zacznę od tego, że prawidłowa odpowiedz nie jest mi na razie znana, ale ja odpowiedzi na pytanie Malakina szukałbym w tym, że
1.
istnieją trzy specjalne operatory  . , -> , ::
pierwsze dwa ( . , -> ) odnoszą się do elementu klasy - tu by trzeba było poszukać dokładnych definicji i ich znaczeń
a :: zwany operatorem zakresu  i on odnosi się do klasy rodzica (metod, własności
w zacytowanym przykładzie klasa string ma konstruktor string, ale klasa rodzica object (tu przypuszczam, że object) raczej tego nie ma (to jest na 99% pewne)

2.
no ale konstruując klasę
C/C++
// plik A.h
class A {
    A(..); // mamy deklarację konstruktora
    ...
};

//plik A.cpp

A::A(...) {...} // realizacja klasy  konstrukcja z :: jest prawidłowa

tak więc A::A ma sens
dlaczego więc nie możemy się do niego odwołać w ten sposób w programie ?
tutaj najlepszą odpowiedzią jest chyba że twórca/y języka to założyli z ważnych powodów

dowód przez zaprzeczenie
np. jeśli mielibyśmy taką możliwość to moglibyśmy w programie modyfikować konstruktor klasy (tak jak jest nam to wygodnie) lub
dodać nowy konstruktor i wpływać na budowę klasy
pozwalałoby to też na dostęp do własności prywatnych
a to kłóciłoby się z koncepcją hermetyczności klasy

3.
Bjerne Stroustrup w książce "język C++"
informuje, że wywołanie :: dla metod wirtualnych jest nieprawidłowe ponieważ prowadzi do zapętlenia

po przeczytaniu tego zacząłem się zastanawiać, czy konstruktor nie jest przypadkiem taką metodą wirtualną
*nie - bo wymagane jest, że metoda virtual powinna być pusta, a kontruktor rodzica nie musi być pusty
*tak - konstruktor zawsze musi istnieć, albo programuje go programista, albo tworzony jest automatycznie w procesie kompilacji
   fakt, że zawsze musi istnieć.
podsumowując p3 chyba to nie ten przypadek :: nad którym zastanawiamy się.

4.
trzeba by też zastanowić się nad różnicami użycia jak r-wartość lub l-wartość
tu pewnie są różnice, ale to tylko przeczucie

5.
na
http://cpp0x.pl/kursy​/Programowanie-obiektowe-C++​/Podstawy/Operatory/498
jest :: Operator konwersji
Przy deklarowaniu operatora konwersji nie podaje się typu zwracanego. Jest on niejawnie taki sam, jak typ podany w nazwie operatora. Może być zdefiniowany wyłącznie jako metoda.

zastanowiło mnie to bo w innych miejscach opisuje się go jako operator zakresu
? i rozumiem i nie rozumiem jednocześnie


Podsumowując wydaje mi się z operatorem:: jest jak z pojęciem "static"
przy static doliczyłem się chyba z 7 różnych znaczeń w zależności od miejsca użycia
dlatego wiele odpowiedzi może być prawdziwych w odpowiednich kontekstach użycia.


Tak jak napisałem wcześniej są to chyba bardziej dywagacje, niż poprawna odpowiedz na poruszony problem



 

P-123312
Kaikso
» 2014-12-25 14:57:01
Sprawdziłem jak to właściwie jest z tymi konstruktorami i proszę bardzo oto wynik:

Najpierw napisałem plik test.cpp:

C/C++
#include <cstdio>

class Klasa
{
public:
    Klasa(); //konstruktor domyślny
    Klasa( Klasa && k ); //konstruktor przenoszący
    Klasa( const Klasa & k ); //konstruktor kopiujący
    ~Klasa(); //destruktor
    void Metoda(); //metoda
};

Klasa::Klasa()
{
    puts( "Konstruktor domyślny" );
}

Klasa::Klasa( Klasa && k )
{
    puts( "Konstruktor przenoszący" );
}

Klasa::Klasa( const Klasa & k )
{
    puts( "Konstruktor kopiujący" );
}

Klasa::~Klasa()
{
    puts( "Destruktor" );
}

void Klasa::Metoda()
{
    puts( "Metoda" );
}

Po zastanowieniu co ja teraz z tym mogę zrobić wykonałem polecenie:

g++ -c -S --std=c++0x -masm=intel test.cpp -o test.s

A oto wynik plik - test.s (asembler w składni intela):

.file "test.cpp"
.intel_syntax noprefix
.section .rodata
.LC0:
.string "Konstruktor domy\305\233lny"
.text
.align 2
.globl _ZN5KlasaC2Ev
.type _ZN5KlasaC2Ev, @function
_ZN5KlasaC2Ev:
.LFB1:
.cfi_startproc
push ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
mov ebp, esp
.cfi_def_cfa_register 5
sub esp, 24
mov DWORD PTR [esp], OFFSET FLAT:.LC0
call puts
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE1:
.size _ZN5KlasaC2Ev, .-_ZN5KlasaC2Ev
.section .rodata
.LC1:
.string "Konstruktor przenosz\304\205cy"
.text
.align 2
.globl _ZN5KlasaC2EOS_
.type _ZN5KlasaC2EOS_, @function
_ZN5KlasaC2EOS_:
.LFB4:
.cfi_startproc
push ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
mov ebp, esp
.cfi_def_cfa_register 5
sub esp, 24
mov DWORD PTR [esp], OFFSET FLAT:.LC1
call puts
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE4:
.size _ZN5KlasaC2EOS_, .-_ZN5KlasaC2EOS_
.section .rodata
.LC2:
.string "Konstruktor kopiuj\304\205cy"
.text
.align 2
.globl _ZN5KlasaC2ERKS_
.type _ZN5KlasaC2ERKS_, @function
_ZN5KlasaC2ERKS_:
.LFB7:
.cfi_startproc
push ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
mov ebp, esp
.cfi_def_cfa_register 5
sub esp, 24
mov DWORD PTR [esp], OFFSET FLAT:.LC2
call puts
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE7:
.size _ZN5KlasaC2ERKS_, .-_ZN5KlasaC2ERKS_
.section .rodata
.LC3:
.string "Destruktor"
.text
.align 2
.globl _ZN5KlasaD2Ev
.type _ZN5KlasaD2Ev, @function
_ZN5KlasaD2Ev:
.LFB10:
.cfi_startproc
push ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
mov ebp, esp
.cfi_def_cfa_register 5
sub esp, 24
mov DWORD PTR [esp], OFFSET FLAT:.LC3
call puts
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE10:
.size _ZN5KlasaD2Ev, .-_ZN5KlasaD2Ev
.section .rodata
.LC4:
.string "Metoda"
.text
.align 2
.globl _ZN5Klasa6MetodaEv
.type _ZN5Klasa6MetodaEv, @function
_ZN5Klasa6MetodaEv:
.LFB12:
.cfi_startproc
push ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
mov ebp, esp
.cfi_def_cfa_register 5
sub esp, 24
mov DWORD PTR [esp], OFFSET FLAT:.LC4
call puts
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE12:
.size _ZN5Klasa6MetodaEv, .-_ZN5Klasa6MetodaEv
.globl _ZN5KlasaC1Ev
.set _ZN5KlasaC1Ev,_ZN5KlasaC2Ev
.globl _ZN5KlasaC1EOS_
.set _ZN5KlasaC1EOS_,_ZN5KlasaC2EOS_
.globl _ZN5KlasaC1ERKS_
.set _ZN5KlasaC1ERKS_,_ZN5KlasaC2ERKS_
.globl _ZN5KlasaD1Ev
.set _ZN5KlasaD1Ev,_ZN5KlasaD2Ev
.ident "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3"
.section .note.GNU-stack,"",@progbits


Pod dokonaniu niezbyt wnikliwej analizy widać że to co napisałem wyżej w większości (tak na 80%+) nie ma pokrycia z rzeczywistością :P
Kod konstruktorów jest w 100% identyczny z kodem metody (tego się nie spodziewałem :/).

Teoretycznie skoro tak to wygląda w skompilowanym kodzie to możliwe było by wywołanie konstruktora jak zwykłej funkcji.
Pewnie można było by to zrobić poprzez rzutowanie adresów na inne klasy i przekazane ich między modułami, ale prawie napewno jako element zabezpieczenia wikłanie nazw klas odbywa się w inny sposób. Można też iść na łatwiznę i zrobić coś takiego:
C/C++
//
// Deklarujemy konstruktory i metodę jako funkcje.
// Do tego musimy wyłączyć wikłanie.
//

extern "C" //Wikłanie wyłączone
{
   
    extern void _ZN5KlasaC2Ev( void ); // konstruktor domyślny
    extern void _ZN5KlasaC2EOS_( void ); // konstruktor przenoszący
    extern void _ZN5KlasaC2ERKS_( void ); // konstruktor kopiujący
    extern void _ZN5KlasaD2Ev( void ); // destruktor
    extern void _ZN5Klasa6MetodaEv( void ); // metoda
}

int main()
{
    _ZN5KlasaC2Ev();
    _ZN5KlasaC2EOS_();
    _ZN5KlasaC2ERKS_();
    _ZN5KlasaD2Ev();
    _ZN5Klasa6MetodaEv();
    return 0;
}

Wykonujemy polecenia:

g++ --std=c++0x -c test.cpp
g++ --std=c++0x -c main.cpp
gcc main.o test.c -o a.out
./a.out
A odo wynik:

Konstruktor domyślny
Konstruktor przenoszący
Konstruktor kopiujący
Destruktor
Metoda

I jak pięknie działa :)
Pewnie to wikłanie to jakaś prosta sprawa i dało by się to zrobić za pomocą
__attribute__(( __strong__, __alias__( "nazwa" ) ) )
, a zapewne nie jest to zbyt skomplikowany schemat więc nazwę mogła by generować jakaś funkcja jako parametr do __attribute__.

I jeszcze jedno pewnie wskaźnik
this
 jest zwykłym argumentem ale niech każdy sprawdza to na własną rękę.
P-123315
Monika90
» 2014-12-25 15:05:26
Z góry uprzedzam że się nie znam i mnie to nawet za bardzo nie interesuje, bo nie ma to znaczenia praktycznego, ale moje przypuszczenia są takie:

Otóż jeżeli masz coś takiego
C/C++
T( argumenty )
to T nie jest nazwą konstruktora, T jest nazwą typu. Tworząc obiekty w C++ podajemy nazwę typu obiektu, nie wywołujemy konstruktora wprost.

Natomiast tu:
C/C++
T::T( argumenty )
T::T odnosi się do konstruktora i jest to wywołanie konstruktora. Ale UWAGA: w C++ nie można wywołać konstruktora bezpośrednio, konstruktor to funkcja specjalna i tylko kompilator może go wywołać, dlatego to się nie skompiluje (chociaż clang to kompiluje, nie widzieć czemu, pewnie interpretuje drugie T jako injected-class-name).


1.) Dzięki operatorowi zakresu `::' można się dostać do metod i pól statycznych klasy, a konstruktor nie jest statyczny.
Do niestatycznych też, i do typów składowych i klas podstawowych też.
P-123316
Kaikso
» 2014-12-25 15:18:57
1.) Dzięki operatorowi zakresu `::' można się dostać do metod i pól statycznych klasy, a konstruktor nie jest statyczny.
Do niestatycznych też, i do typów i klas podstawowych też.
Naprawdę spróbuj to skompilować:
string::c_str()
Wydawało mi się że to niepoprawny zapis :/ - nie zrozumiałaś mnie, nie chodziło mi o stosowanie przy deklaracjach.
P-123317
Monika90
» 2014-12-25 16:38:00
A Ty spróbuj skompilować to
C/C++
struct X
{
    int i;
    void f() { X::i = 0; }
    void g()
    {
        X::f();
        this->X::f();
        ( * this ).X::f();
    }
};
:)
P-123319
Elaine
» 2014-12-25 17:04:42
Naprawdę spróbuj to skompilować:
string::c_str()
U mnie się kompiluje:
C/C++
#include <string>

struct foo
    : std::string
{
    using std::string::string;
    void f() {
        std::string::c_str();
    }
};
P-123320
« 1 » 2
  Strona 1 z 2 Następna strona