Как заставить созданный компилятором конструктор копирования класса *не * быть встроенным компилятором?

альтернативный заголовок вопроса будет: как явно заставить компилятор генерировать код для конструкторов, созданных компилятором, в определенной единице перевода?

проблема в том, что для одного кода в результате -- тщательно измерены -- производительность лучше (примерно на 5%), если вызовы copy-ctor одного объекта не inlined, то есть если этот конструктор реализован вручную. (Мы заметили это, потому что во время очистка кода избыточная явно реализованная копия ctor этого класса (17 членов) была удалена.)

Edit: обратите внимание, что мы есть проверил сгенерированный код сборки и убедился, что встраивание и генерация кода происходит так, как я описываю для двух разных версий кода.

теперь мы сталкиваемся с выбором: просто вернуть ручной код copy-ctor (он делает то же самое, что и созданный компилятором) или найти любой другие способы не вставка копии ctor этого класса.

существуют ли какие-либо средства (для Microsoft Visual C++) для явного создания экземпляра созданных компилятором функций класса в определенной единице перевода или они всегда будут встроены в каждую единицу перевода, где они используются? (Комментарии для gcc или других компиляторов также приветствуются, чтобы получить лучшую картину ситуации.)


так как первые 2 ответа показывают некоторое недоразумение: компилятором функции класса генерируются только самим компилятором, если они не объявлены и не определены пользователем. Поэтому никакие модификаторы не могут быть применены к ним, так как эти функции отсутствуют в исходном коде.

struct A {
  std::string member;
};

A имеет значение по умолчанию и copy ctor, dtor и оператор копирования. Ни одна из этих функций не может быть изменена через некоторый declspec, потому что они не существуют в код.

struct B {
  std::string member;
  B(B const& rhs);
};

B Теперь пользователем копию ctor, и пользователь должен его реализовать. Компилятор не будет генерировать код для него.


еще немного фона для сомневающихся : -)...

этот код компилируется с помощью MS Visual C++, но он связан для встроенной(-like) (realtime) системы. Производительность была измерена, принимая тайминги по этой системе, и поэтому я думаю, что ребята, которые взяли тайминги, будут иметь приличные числа.

тест был выполнен путем сравнения двух версий кода, где только разница была встроенной и не встроенной копией ctor этого одного класса. Тайминги с встроенным кодом были хуже примерно на 5%.


дальнейшая проверка показала, что я ошибся в одном пункте: компилятор будет создание отдельных функций для сложных конструкторов копирования. Он будет делать это по своему усмотрению, а также зависит от параметры оптимизации. Поэтому в нашем случае компилятор делает что-то не так в наших конкретных обстоятельствах. Из ответов пока не видно, что мы можем сказать компилятору иначе. :-(

6 ответов


12.1$/5- "неявно объявлен конструктор по умолчанию встроенный общественный член своего класса.".

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

сказав, что

вполне вероятно, что ваш конструктор ручного копирования (который вы удалили во время очистки кода) делал все правильно. Например, если один из членов (из 17) в вашем классе является членом указателя, вполне вероятно, что конструктор ручного копирования позаботился о глубокой копии (и, следовательно, ударил по производительности).

Итак, если вы не внимательно просмотрите свой конструктор ручной копии, даже не думайте об его удалении и полагаться на (потенциально багги) неявный конструктор копирования (в вашем контексте)


Я очень сомневаюсь, что инлайнинг имеет к этому какое-то отношение. Если компилятор вводит созданный компилятором экземпляр ctor, почему бы ему также не встроить явно определенный? (Также необычно, что эвристика оптимизации компилятора не работает так плохо, чтобы сделать встроенный код на 5% медленнее)

прежде чем делать поспешные выводы,

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

Если это так, не могли бы вы обновить ваш вопрос с этой информацией?

В C++ нет способа указать, должна или не должна быть встроена функция, созданная компилятором. Даже не вендора расширения, такие как __declspec(noinline) поможет вам в этом, так как вы явно передаете всю ответственность за функцию компилятору. Поэтому компилятор выбирает, что с ним делать, как его реализовать и следует ли его встроить. Вы не можете одновременно сказать "пожалуйста, реализуйте эту функцию для меня "и в то же время"пожалуйста, позвольте мне контролировать, как функция реализуется". Если вы хотите контролировать функцию, вы должны ее реализовать. ;)

В C++0x это мая будет возможно (в зависимости от того, как эти расширения для конкретного поставщика взаимодействуют с функциями, объявленными как = default).

но опять же, я не уверен, что проблема в подложке. Скорее всего, две функции просто приводят к созданию разного кода сборки.


_ _ declspec (noinline).

в документации говорится, что она применяется только к функциям-членам, но на самом деле она работает и со свободными функциями.


часто лучше всего изолировать его до нескольких основных типов, которые, как вы знаете, проблематичны. пример:

class t_std_string {
    std::string d_string;
public:
    /* ... */

    /* defined explicitly, and out of line -- you know what to do here */
    t_std_string();
    t_std_string(const std::string& other);
    t_std_string(const t_std_string& other);
    ~t_std_string();

    inline std::string& get() { return this->d_string; }
    inline const std::string& get() const { return this->d_string; }
    /* ... */
};

struct B {
    t_std_string member;
    /* 16 more */
    /* ... */
};

или вы можете взять некоторые из них бесплатно. пример б:

/* B.hpp */

struct B {
private:

    /* class types */
    struct t_data {
        std::string member;

        /* 16 more ... */
    public:
        /* declare + implement the ctor B needs */

        /* since it is otherwise inaccessible, it will only hurt build times to make default ctor/dtor implicit (or by implementing them in the header, of course), so define these explicitly in the cpp file */
        t_data();
        ~t_data();

        /* allow implicit copy ctor and assign -- this could hurt your build times, however. it depends on the complexity/visibility of the implementation of the data and the number of TUs in which this interface is visible. since only one object needs this... it's wasteful in large systems */
    };
private:

    /* class data */
    t_data d_data;
public:
    /* you'll often want the next 4 out of line
       -- it depends on how this is created/copied/destroyed in the wild
     */
    B();
    B(const B& other);
    ~B();
    B& operator=(const B&);
};

/* B.cpp */

/* assuming these have been implemented properly for t_data */
B::B() : d_data() {
}

B::B(const B& other) : d_data(other) {
}

B::~B() {
}

B& B::operator=(const B&) {
    /* assuming the default behaviour is correct...*/
    this->d_data = other.d_data;
    return *this;
}
/* continue to B::t_data definitions */

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

class some_object_wrapper {
    original_object obj;
    __declspec(noinline) some_object_wrapper(const some_object_wrapper& ref) 
        : obj(ref) {}
    // Other function accesses and such here
};

Если вы в отчаянии, вы можете скомпилировать рассматриваемый класс отдельно в a .lib и ссылка на него. Изменение его на другую единицу перевода не остановит VC++ от его вставки. Кроме того, я должен спросить, действительно ли они делаю то же самое. Почему вы реализовали конструктор копирования вручную, если он делает то же самое, что и конструктор копирования по умолчанию?


добавить свой собственный вывод и ответить на точный вопрос не вдаваясь в подробности:

  1. вы не может силу компилятор, в частности VC++, для встроенного или не встроенного компилятора, генерируемого ctor/dtor / etc. -- но

  2. оптимизатор выберет-по своему усмотрению - если он вводит код для сгенерированной компилятором функции (ctor) или если он генерирует "реальную" функцию для этого кода. AFAIK повлиять на решение оптимизатора в этом отношении невозможно.