LINUX.ORG.RU

Изменение private элементов vs скорость сборки

 


0

2

Вчера подняли тему скорости сборки после внесения изменений в проект. И вопрос, что надо просто правильно писать, чтобы после незначительных изменений быстро пересобиралось.

А как быть с изменением private элементов класса, который является базовым для нескольких других классов? Ведь такое изменение придётся сделать в заголовочном файле. И несмотря на то, что оно по определению не может повлиять на другие классы, make этого не знает и оценит только время модификации .h. Или кто-то кроме make'а уже умеет заглядывать в .h?

★★★★★

Делаются forward-объявления private-части класса (типа MyClassPrivate), объявляется поле с таким типом, реализация делается в отдельной паре .h+.cpp или прямо рядом с реализацией основного класса, что удобнее.

note173 ★★★★★
()

Такое изменение не меняет логическую структуру класса, но ломает бинарную структуру объектов этого класса, так что без пересборки в этом случае не обойтись.

Как это обойти правильно подсказал note173.

Примеры использования подобного подхода можно посмотреть, например, в исходниках Qt.

trex6 ★★★★★
()
Ответ на: комментарий от note173

PImpl

Это же называется PImpl. Для этого и придумали паттерны.

schizoid ★★★
()

CPP разве не умеет генерировать депсы для сорцов? На дворе вроде 12ый год

mashina ★★★★★
()

надо просто правильно писать, чтобы после незначительных изменений быстро пересобиралось.

Как уже правильно сказали - Pimpl, это не только ускоряет компиляцию, но и обеспечивает двоичную совместимость и улучшает изоляцию деталей реализации базового класса от его потомков.

И несмотря на то, что оно по определению не может повлиять на другие классы

Может. Поиск имени осуществляется до проверки доступа:

void foo();

class A
{
  void foo();
};

class B : public A
{
  void bar()
  {
    foo(); // Вызов A::foo()
  }
};
Begemoth ★★★★★
()

Если непременно хочется наследовать реализацию, то нужнро использовать pimpl.

Если достаточно наследования интерфейсов и композиции, то все реализации скрываешь за фабриками.

Manhunt ★★★★★
()

А как быть с изменением private элементов класса, который является базовым для нескольких других классов? Ведь такое изменение придётся сделать в заголовочном файле. И несмотря на то, что оно по определению не может повлиять на другие классы

Изменяется размер объекта класса. А это может повлиять на весь код, где подключается заголовочный с классом.

С помощью слов private, protected вы ничего не инкапсулируете. Наследуемые классы и прочие пользователи должны по прежнему все про класс знать.

ien
()
Ответ на: комментарий от slackwarrior

спасибо за детализацию терминов.

агрегация более широкое понятие= включение (обьект можно сделать видимым мона не видимым из вне(дети во вне))

инкапсуляция = обволакивание (включение и сокрытие «помещения в капсуль»)

т.е всякая инкапсуляция агрегация . не всякая агрегация инкапсуляция.

схоластика end.

ты хотел узнать?

qulinxao ★★☆
()
Ответ на: комментарий от slackwarrior

кстати как уже выше отметили в статических (на этапе компиляции необходимо размещать поля ) - как следствие инкапсуляция скрывающая от наследников (и не требующая ребилда вслучае изменения внутреней организации влекущая за собой измение размера полей и как следствие бинарного отиска наследника) должна хранить в своём отиске либо поля постояного размера либо указатели которые ...

но добавления скрытого от наследника поля не должно требовать перекомпиляции наследника - следовательно все локальные родителю поля должны быть скрыты в поле = обьекте хранящем поля и привязаному к родителю динамически

----------------------------- всё это показывает что реальне обьектное програмирование с магией инкапсуляции(скрытие реальне) обязано быть с динамической памятью

а С++ это оно самое. на асемблере тоже можно . но там синтаксис не такой переносимый

qulinxao ★★☆
()
Ответ на: комментарий от qulinxao

Почему ты не можешь писать грамотно? У тебя дефект речи? Или умственное заболевание? Я серьезно спрашиваю - что тебе доктора говорят?

anonymous
()
17 июня 2012 г.
Ответ на: комментарий от note173

Всё никак не удавалось вернуться к дискуссии.

Спаисбо за совет, также trex6, schizoid. Действительно есть такой «шаблон» pimpl.

А компиляторописатели о нём знают? Я ж не на С программирую в gobject, где, кажись, так и сделано (_private-файлы), а на С++. Лично я не вижу причины, почему компилятор не производит достаточную изоляцию для тех элементов, которые я поместил в private. А она есть?

(Прошу ещё раз заглянуть сюда qulinxao, ien, slackwarrior).

gag ★★★★★
() автор топика
Ответ на: комментарий от Begemoth

И несмотря на то, что оно по определению не может повлиять на другие классы

Может. Поиск имени осуществляется до проверки доступа:

Т.е. проверяются даже те имена, которые всё равно не могут быть вызваны из-за спецификатора доступа? Звучит нерационально, или я не так понял?

gag ★★★★★
() автор топика
Ответ на: комментарий от gag

Так уже ведь объяснили, при изменении набора полей (не важно, приватных или нет) меняется фомат объекта в памяти, поэтому старый объектный код работать не будет. А пересобирается все просто потому, что время последнего изменения заголовочного файла делает устаревшими и все файлы, в которых он включен.

note173 ★★★★★
()
Ответ на: комментарий от gag

Т.е. проверяются даже те имена, которые всё равно не могут быть вызваны из-за спецификатора доступа?

Да

Звучит нерационально, или я не так понял?

Я думаю, что рациональность в неусложнении языка, скажем перегрузка функций и так не самая простая часть языка, а, вероятно, выгоды от её усложнения для учёта квалификаторов доступа не были очевидны/существенны чтобы поёти на дополнительное усложнение. Это ведь усложнение не только для разработчиков компиляторов, но и для остальных программистов.

Begemoth ★★★★★
()
Ответ на: комментарий от gag

Лично я не вижу причины, почему компилятор не производит достаточную изоляцию для тех элементов, которые я поместил в private.

Причина тому - эффективность, если тебе требуется изолировать компоненты - ты это можешь это сделать, но компилятор не сможет встроить вызовы функций-членов, добавиться лишняя косвенность при работе с объектом, и наоборот - если тебе нужна производительность в ущерб двоичной совместимости - это ты тоже можешь сделать.

Begemoth ★★★★★
()
Ответ на: комментарий от note173

А это не в процессе линковки определяется?

Можно как-то продемонстрировать этот эффект?

t.h

#ifndef __t_h__
#define __t_h__

class T
{
        int a_;

        public:
        int b_;

        private:
        int c_;

        public:
        T();

        int a();
        int b();
        int c();
};

#endif // __t_h__
t.cpp
#include "t.h"

T::T(): a_(1), b_(2), c_(3)
{
}

int T::a()
{
        return a_;
}

int T::b()
{
        return b_;
}

int T::c()
{
        return c_;
}
test.cpp
#include <iostream>

#include "t.h"

int main(int argc, char *argv[])
{
        T t;

        std::cout << "a=" << t.a() << ", b=" << t.b() << ", c=" << t.c() << std::endl;

        return 0;
}
Makefile
CXX=g++
CXXFLAGS=-Wall -g

OBJS=t.o test.o

all: test

t.o: t.cpp t.h
	$(CXX) $(CXXFLAGS) -c $<

test.o: test.cpp
	$(CXX) $(CXXFLAGS) -c $<

test: $(OBJS)
	$(CXX) $(LDFLAGS) -o $@ $^

Скажем, добавляя какие-нибудь d_, e_ в private, не перекомпилируя test.cpp не меняют результата. А что можно сделать, чтобы нарушить бинарную совместимость?

gag ★★★★★
() автор топика
Ответ на: комментарий от gag

t.h

#ifndef __t_h__
#define __t_h__

class T
{
private:
        int foo1;
        int foo2;

public:
        int field;

public:
        T();

        void printAddress();
};

#endif // __t_h__

t.cpp

#include "t.h"

#include <iostream>

T::T()
{
}

void T::printAddress()
{
        T *t = NULL;
        unsigned long long addrField = (unsigned long long)((void *)&(t->field));

        std::cout << "t.cpp: addr = " << addrField << std::endl;
}

test.cpp

#include <iostream>

#include "t.h"

int main(int argc, char *argv[])
{
        T *t = NULL;
        unsigned long long addrField = (unsigned long long)((void *)&(t->field));

        std::cout << "main: addr = " << addrField << std::endl;

        T *t2 = new T();
        t2->printAddress();

        return 0;
}

$ make
air-laptop:test peter$ make
g++ -Wall -g -c test.cpp
g++  -o test t.o test.o
air-laptop:test peter$ ./test
main: addr = 8
t.cpp: addr = 8

Меняем t.h

class T
{
private:
        int foo1;
        int foo2;
        int foo3; //<--
...

air-laptop:test peter$ make test
g++ -Wall -g -c t.cpp
g++  -o test t.o test.o
air-laptop:test peter$ ./test
main: addr = 8
t.cpp: addr = 12
note173 ★★★★★
()
Ответ на: комментарий от note173

Спасибо за пример. Хотелось бы его изменить так, чтобы не было public переменных, а доступ к ним (a_) был только через get'еры (a()). И вставив между, скажем, a_ и b_ - c_ получить по вызову c() - содержимое не c_, а b_. Тогда всё стало бы совсем очевидно.

gag ★★★★★
() автор топика
Ответ на: комментарий от gag

Блин, ну элементарно же:

foo.hh

class Foo
{
  int a;
  int c;
public:
  Foo(int a, int c) : a(a), c(c) { }

  int get_a() const { return a; }
  int get_c() const { return c; }
};

void baz(const Foo&);

foo.cc

#include <iostream>
#include "foo.hh"

void baz(const Foo& f)
{
  std::cout << f.get_a() << ' ' << f.get_c() << std::endl;
}

main.cc

#include "foo.hh"

int main()
{
  baz(Foo(1, 2));
}

Собираем и запускаем:

max@neptune ~ % g++ -c foo.cc 
max@neptune ~ % g++ -c main.cc 
max@neptune ~ % g++ -o foo main.o foo.o
max@neptune ~ % ./foo 
1 2

Добавляем поле в Foo:

foo.hh

class Foo
{
  int a;
  int b;
  int c;
public:
  Foo(int a, int b, int c) : a(a), b(b), c(c) { }

  int get_a() const { return a; }
  int get_c() const { return c; }
};

void baz(const Foo&);

main.cc

#include "foo.hh"

int main()
{
  baz(Foo(1, 42, 2));
}

Пересобираем только main.cc:

max@neptune ~ % g++ -c main.cc 
max@neptune ~ % g++ -o foo main.o foo.o
max@neptune ~ % ./foo 
1 42

Begemoth ★★★★★
()
Ответ на: комментарий от note173
std::cout << "addr getField=" << &(t->getField) << std::endl;
test.cpp:9:48: error: ISO C++ forbids taking the address of a bound member function to form a pointer to member function.  Say ‘&T::getField’ [-fpermissive]

Ага, т.е. взять адрес метода как раз и запрещено (хотя и можно). Значит, если интерфейс с классом осуществляется только посредством вызова методов, что и является правильным стилем, то, бинарная совместимость не нарушается.

Остаётся проверить наследование.

gag ★★★★★
() автор топика
Ответ на: комментарий от Begemoth

Согласен, этот пример тривиален: ведь изменён также public-интерфейс. Но я веду речь только о поломке при изменении исключительно private-элементов. И в примере следует использовать только «рекомендованный» C++ стиль (например, никаких public переменных).

gag ★★★★★
() автор топика
Ответ на: комментарий от Begemoth

Пересобираем только main.cc:

Речь как раз о пересборе класса (поменяли что-то в private), но не пересобираем весь проект из-за этого. Только линковка, разумеется.

gag ★★★★★
() автор топика
Ответ на: комментарий от gag

Да, методы никак не влияют на формат объекта в памяти (если они не виртуальные). Но в данной ситуации виртуальные еще и лучше, они разрешаются во время выполнения, а не компиляции.

note173 ★★★★★
()
Ответ на: комментарий от gag

Согласен, этот пример тривиален: ведь изменён также public-интерфейс.

Ну оставим сигнатуру конструктора прежней:

Foo(int a, int c) : a(a), b(42), c(c) { }

Ничего не изменится.

Речь как раз о пересборе класса

В современных реализациях С++ нет понятия «пересборка класса». Можно в определении класса только объявить функции-члены, а реализации вынести в другую единицу трансляции, но тут мы получаем частичную двоичную совместимость, за счёт того, что компилятор не может встроить вызовы функций-членов.

Begemoth ★★★★★
()
Ответ на: комментарий от gag

поменяли что-то в private

Ты же понимаешь, что это изменит формируемые по умолчанию конструктор копирования, оператор присваивания и деструктор, а также размер объекта, который передаётся оператору new и используемый для выделения памяти под объект в стеке, а так же то, что эти умолчальные функции компилируются в точках их использования?

Begemoth ★★★★★
()
Ответ на: комментарий от gag

Ага, т.е. взять адрес метода как раз и запрещено

Агащазблин. Адрес метода надо брать привильно, о чём компилятор и говорит: не &(t->getField), а &T::getField.

Begemoth ★★★★★
()
Ответ на: комментарий от Begemoth

нет понятия «пересборка класса»

Да-да, это я сократил от «перекомпиляции исходника, содержащего реализацию класса».

компилятор не может встроить вызовы функций-членов.

Имеется ввиду, что компилятор может сам встроить (implicit inline?) код private-методов в методы другого (зависимого) класса, сделав получившийся объектник таки зависимым от private-элементов исходного класса, private-элементы которого я и хочу менять без влияния на другие использующие классы?

У нас с note173 пока не вышло привести пример нарушения двоичной совместимости при вменяемом использовании интерфейсов. А интересно всё таки сочинить такой маленький примерчик, который, похоже, надо не с -g, а с -O2 компилировать, чтобы получить выше описанное поведение.

В случае с C использовал бы gcc -S / objdump, чтобы увидеть внутренности. А с С++ реально разобраться или есть ещё какой dump?

gag ★★★★★
() автор топика
Ответ на: комментарий от Begemoth

Точно :) (хоть с -fpermissive сработало также).

gag ★★★★★
() автор топика
Ответ на: комментарий от gag

Да-да, это я сократил от «перекомпиляции исходника, содержащего реализацию класса».

Да вот нет такого исходника. Хотя бы потому что, выделение памяти под объекты на стеке в программе вообще нигде никак не реализуется, а для такого выделения компилятору нужно знать размер объекта, а он определяется количеством и типами полей класса и его предками.

У нас с note173 пока не вышло привести пример нарушения двоичной совместимости при вменяемом использовании интерфейсов.

Если ты в определении класс ни одну функцию не реализуешь, а также сам реализуешь все формируемые по-умолчанию функции (конструкторы по-умолчанию и копирования, опрератор присваивания, деструктор), то единственной проблемой для обеспечения двоичной совместимости остаётся размер объекта.

foo.hh:

class Foo
{
  int a;
  int c;

  // мне лень их реализовывать, но смысл не меняется.
  Foo(const Foo&);
  void operator= (const Foo&);
public:
  Foo();
  ~Foo();

  int get_a() const;
  int get_c() const;
};

foo.cc

#include "foo.hh"

Foo::Foo()
  : a(1), c(3)
{ }

Foo::~Foo()
{ }

int Foo::get_a() const
{
  return a;
}

int Foo::get_c() const
{
  return c;
}

main.cc

#include <iostream>
#include "foo.hh"

int main()
{
  int a = 100;
  int b = 200;
  Foo f;
  int c = 300;
  int d = 400;

  std::cout << a << ' ' << b << ' ' << f.get_c() << ' ' << c << ' ' << d << std::endl;
}

Запускаем:

max@neptune ~ % g++ -c foo.cc          
max@neptune ~ % g++ -c main.cc 
max@neptune ~ % g++ -o foo main.o foo.o
max@neptune ~ % ./foo
100 200 3 300 400

Изменяет закрытые члены класса:

foo.hh

class Foo
{
  int a;
  int b;
  int c;

  // мне лень их реализовывать, но смысл не меняется.
  Foo(const Foo&);
  void operator= (const Foo&);
public:
  Foo();
  ~Foo();

  int get_a() const;
  int get_c() const;
};

foo.cc

#include "foo.hh"

Foo::Foo()
  : a(1), b(2), c(3)
{ }

Foo::~Foo()
{ }

int Foo::get_a() const
{
  return a;
}

int Foo::get_c() const
{
  return c;
}

Пересобирает foo.cc и перекомпоновываем:

max@neptune ~ % g++ -c foo.cc          
max@neptune ~ % g++ -o foo main.o foo.o
max@neptune ~ % ./foo 
3 200 3 300 400

Упс, переменная a в main() равна 3 :-(

Begemoth ★★★★★
()
Ответ на: комментарий от Begemoth

вменяемом использовании интерфейсов.

Жду определения вменяемости как закрытия всех конструкторов и определения фабричной функции, которая возвращает указатель на объект :-)

Begemoth ★★★★★
()
Ответ на: комментарий от korvin_

речь о снижении зависимости по коду ( но так как в компилируемых статически футпринт встраивается статически(как правило) вместо ссылки на обьект с известным во вне интерфейсом) то и в случае включения футпринт обьемлющего обьекта меняется при изменении футпринта включаемого.

но чиста «абстрактно» наследование как оно реализованно в С++ приводит к подьёму частных свойств к обьектному корню - что очень очень плохо :)

вообще Алан Кей говорит что наследование это зло :)

qulinxao ★★☆
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.