• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

cout - jak powstaje?

Object Storage Arubacloud
+3 głosów
783 wizyt
pytanie zadane 11 grudnia 2018 w C i C++ przez Lebowski Początkujący (330 p.)

Cześć,

Chcę Was prosić o pomoc w zrozumieniu jak powstaje obiekt cout.

Wiem jedynie, że cout jest obiektem klasy std::ostream i powstaje w konstruktorze klasy std::ios_base::Init (tzn. w konstruktorze pewnej klasy wewnętrznej klasy std::ios_base) - natomiast obiekt klasy std::ios_base::Init powstaje w pliku nagłówkowym <iostream>.

Ale klasa std::ios_base jest klasą bazową klasy std::ostream - więc konstruktor wewnętrznej klasy w klasie bazowej nie zna klasy pochodnej std::ostream. Zatem w jaki sposób może tworzyć jej obiekt?

 

3 odpowiedzi

0 głosów
odpowiedź 12 grudnia 2018 przez Arkadiusz Sikorski Pasjonat (20,160 p.)
wybrane 30 grudnia 2018 przez Lebowski
 
Najlepsza

Ale klasa std::ios_base jest klasą bazową klasy std::ostream - więc konstruktor wewnętrznej klasy w klasie bazowej nie zna klasy pochodnej std::ostream. Zatem w jaki sposób może tworzyć jej obiekt?

Nie znam działania samego iostreama, ale klasa wewnętrzna może wiedzieć o istnieniu klasy pochodnej klasy jej zawierającej.

Przykład:

class Base
{
  class Init
  {
     Init();
  };
};

class Stream : public Base {};

Base::Init::Init()
{
    auto s = Stream();  
}

Powyższy kod się kompiluje bezproblemowo i jest semantycznie poprawny. Warto zauważyć, że zaimplementowanie konstruktora na górze (czyli jednoczesna deklaracja i definicja) spowoduje błąd kompilacji - na tamtym etapie kompilator nie wie nic o istnieniu klasy Stream.

1
komentarz 30 grudnia 2018 przez Lebowski Początkujący (330 p.)
Rozumiem - zatem żeby konstruktor wewnętrznej klasy w klasie bazowej znał klasę pochodną wystarczy zainkludować do pliku implementacyjnego klasy bazowej plik nagłówkowy klasy pochodnej. Dzięki.
+1 głos
odpowiedź 12 grudnia 2018 przez RafalS VIP (122,820 p.)

Z chęcią usłysze od kogoś bardziej szczegółowe wyjaśnienie, ale zrobiłem troche testów i coś takiego da się zrobić, bo deklaracja klasy ios_base::init wcale nie musi odwoływać się do klasy ostream. Może  po prostu w konstruktorze edytować globalny obiekt ostream.

Nie twierdze, że tak to jest zrobione, ale pokazuje, że to całkiem możliwe:

ios_base.h:

#pragma once
struct ios_base_ {
	struct init_ {
		init_();
	};
};

ios_base.cpp:

#include "ios_base_.h"
#include "ostream_.h"
ostream_ cout_;
ios_base_::init_::init_() {
    //do sth with cout_
    cout_.initialised = true;
}
static ios_base_::init_ initializer;

ostream.h:

#pragma once
#include "ios_base_.h"
#include <iostream>

struct ostream_ : ios_base_ {
    bool initialised = false;
};
extern ostream_ cout_;

main:

#include<iostream>
#include "ios_base_.h"
#include "ostream_.h"
int main() {
    std::cout << cout_.initialised << std::endl;;
}

 

+1 głos
odpowiedź 30 grudnia 2018 przez Lebowski Początkujący (330 p.)
edycja 6 stycznia 2019 przez Lebowski

Spróbowałem sobie "zamodelować" sposób powstawania obiektu std::cout. Wyczytałem, że wykorzystywany jest do tego celu idiom C++ znany jako "Schwarz Counter" (albo "Nifty Counter").

(Nazwy w kodzie odnoszące się do oryginalnych z STL-a opatrzyłem sufiksem "_m").

ios_m.h:

#ifndef IOS_M_H
#define IOS_M_H

class ios_base_m
{
public:
	//...
    class Init_m
    {
    public:
        Init_m();
        ~Init_m();
        static int nifty_counter;
    };
};

#endif // IOS_M_H

ios_m.cpp:

#include <new>         // placement new
#include <type_traits> // aligned_storage
#include "ostream_m.h"


int ios_base_m::Init_m::nifty_counter;

static typename std::aligned_storage<sizeof (ostream_m), alignof (ostream_m)>::type stream_buf; // memory for the stream object

ostream_m& cout_m = reinterpret_cast<ostream_m&> (stream_buf);

//...

ios_base_m::Init_m::Init_m()
{
    if (nifty_counter++ == 0) new (&cout_m) ostream_m(); // placement new
}

ios_base_m::Init_m::~Init_m()
{
    if (--nifty_counter == 0) (&cout_m)->~ostream_m();
}

ostream_m.h:

#ifndef OSTREAM_M_H
#define OSTREAM_M_H
#include "ios_m.h"

class ostream_m : public ios_base_m
{
public:
    ostream_m();
    ~ostream_m();
    int flag;
	//...
};

#endif // OSTREAM_M_H

ostream_m.cpp:

#include "ostream_m.h"

ostream_m::ostream_m()
{
    flag = 5;
	//...
}

ostream_m::~ostream_m()
{
    flag = 0;
	//...
}

//...

iostream_m.h:

#ifndef IOSTREAM_M_H
#define IOSTREAM_M_H
#include "ostream_m.h"

extern ostream_m& cout_m;

static ios_base_m::Init_m init_m;

#endif // IOSTREAM_M_H

main:

#include <iostream>
#include "iostream_m.h"

using namespace std;

int main()
{
    cout<<cout_m.flag<<endl;
    cout_m.flag = 20;
    cout<<cout_m.flag<<endl;

    return 0;
}

Oczywiście to tylko model, ale jak sądzicie - czy prawidłowy?

Podsumowanie:

Zastanawiałem się tu w jaki sposób powstaje obiekt std::cout, po co bazowej klasie tego obiektu potrzebna jest klasa wewnętrzna ios_base::Init oraz jaką rolę w tym wszystkim odgrywa idiom „Nifty Counter” (albo: „Schwarz Counter”). Chyba ogarnąłem temat :)

Otóż:

Załóżmy, że w naszym projekcie oprócz pliku z main() mamy jeszcze dwie jednostki translacji: A i B. Załóżmy też, że każda z nich tworzy jedną zmienną globalną o nietrywialnej inicjalizacji (tzn. posiadającą określony konstruktor). Okazuje się, że kolejność kompilacji tych jednostek NIE jest ustalona, co oznacza, że kolejność inicjalizacji tych zmiennych nie jest ustalona.

 Załóżmy teraz, że kompilacja jednostki A wyprzedza kompilację jednostki B. Jeżeli konstruktor zmiennej globalnej z jednostki A odwoływał się jakoś do zmiennej globalnej z jednostki B to mamy problem: zmienna z jednostki B nie została jeszcze zainicjalizowana. Inny przykład problemu: konstruktor zmiennej z jednostki A pisze coś do pliku, który otwiera konstruktor zmiennej z jednostki B.

Dlatego właśnie na początku każdej jednostki translacji odwołującej się do obiektu std::cout włączamy plik <iostream>. Plik ten zawiera deklarację zewnętrznego obiektu cout oraz tworzy obiekt klasy ios_base::Init. Konstruktor klasy ios_base::Init sprawdza i postinkrementuje licznik – pole statyczne tej klasy (ten licznik to właśnie „Schwarz Counter”).  Jeżeli licznik równy jest zero to konstruktor inicjalizuje  obiekt cout.

To zapewnia, że:

1).  obiekt cout jest inicjalizowany już podczas kompilacji pierwszej jednostki translacji zawierającej plik <iostream> (i tylko wtedy);

2). obiekt cout jest inicjalizowany PRZED inicjalizacją jakichkolwiek zmiennych globalnych/statycznych występujących w tych jednostkach. I gdyby ich konstruktory odwoływały się do obiektu cout to odwołują się już do zainicjalizowanego obiektu cout.

Tak to chyba jakoś wygląda :)

Podobne pytania

0 głosów
1 odpowiedź 137 wizyt
pytanie zadane 27 maja 2020 w C i C++ przez Hubert_123 Początkujący (380 p.)
0 głosów
1 odpowiedź 2,604 wizyt
pytanie zadane 25 października 2018 w C i C++ przez XezolPL Obywatel (1,530 p.)
0 głosów
1 odpowiedź 404 wizyt

92,576 zapytań

141,426 odpowiedzi

319,652 komentarzy

61,961 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj.

Akademia Sekuraka

Kolejna edycja największej imprezy hakerskiej w Polsce, czyli Mega Sekurak Hacking Party odbędzie się już 20 maja 2024r. Z tej okazji mamy dla Was kod: pasjamshp - jeżeli wpiszecie go w koszyku, to wówczas otrzymacie 40% zniżki na bilet w wersji standard!

Więcej informacji na temat imprezy znajdziecie tutaj. Dziękujemy ekipie Sekuraka za taką fajną zniżkę dla wszystkich Pasjonatów!

Akademia Sekuraka

Niedawno wystartował dodruk tej świetnej, rozchwytywanej książki (około 940 stron). Mamy dla Was kod: pasja (wpiszcie go w koszyku), dzięki któremu otrzymujemy 10% zniżki - dziękujemy zaprzyjaźnionej ekipie Sekuraka za taki bonus dla Pasjonatów! Książka to pierwszy tom z serii o ITsec, który łagodnie wprowadzi w świat bezpieczeństwa IT każdą osobę - warto, polecamy!

...