12

Есть несколько вопросов о переполнении стека по строкам «почему я не могу инициализировать статические члены данных в классе на C++». Большинство ответов цитируют со стандартного сообщения, указывающего вам , что вы можете сделать; те, которые пытаются ответить , почему обычно указывают на ссылку (теперь, казалось бы, недоступную) [EDIT: на самом деле это доступно, см. ниже] на сайте Stroustrup, где он утверждает, что разрешение инициализации статических элементов в классе нарушало бы правило одного определения (УСО).Почему внутриклассическая инициализация статических членов нарушает ODR?

Однако эти ответы кажутся чрезмерно упрощенными. Компилятор отлично справляется с проблемами ODR, когда захочет. Например, рассмотрим следующее в заголовке C++:

struct SimpleExample 
{ 
    static const std::string str; 
}; 

// This must appear in exactly one TU, not a header, or else violate the ODR 
// const std::string SimpleExample::str = "String 1"; 

template <int I> 
struct TemplateExample 
{ 
    static const std::string str; 
}; 

// But this is fine in a header 
template <int I> 
const std::string TemplateExample<I>::str = "String 2"; 

Если я создаю экземпляр TemplateExample<0> в нескольких единицах трансляции, компилятор/компоновщик волшебные пинки в и я получаю ровно одну копию TemplateExample<0>::str в окончательном исполняемого файла.

Итак, мой вопрос заключается в том, что, возможно, компилятор, возможно, решил проблему ODR для статических членов классов шаблонов, почему он не может это сделать и для классов без шаблонов?

EDIT: Ответ на вопросник Stroustrup доступен here. Соответствующее предложение:

Однако, чтобы избежать сложных правил компоновщика, C++ требует, чтобы каждый объект имел уникальное определение. Это правило будет нарушено, если C++ допускает определение класса объектов в классе, которое необходимо сохранить в памяти как объекты

Похоже, что эти «сложные правила компоновщика» существуют и используются в случае шаблона, поэтому почему бы и в простом случае?

+0

C++ 11 расслабляет это ограничение. Вы можете выполнить инициализацию в классе с помощью константных выражений. –

+0

Да, но я понимаю, что даже тогда оно требует определения (без инициализатора) для присутствия в пространстве имен в одной единице перевода; однако в случае шаблона он может отображаться в заголовке и, следовательно, в нескольких TU. Мой вопрос в том, почему символьная коалесцирующая магия, используемая в случае шаблона, не может также использоваться для обычных классов без шаблонов. –

+1

Шаблоны не были частью исходного языка C++. –

ответ

1

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

prototypes.h

class CLASS 
{ 
public: 
    static const int global; 
}; 
template <class T> 
class TEMPLATE 
{ 
public: 
    static const int global; 
}; 

void part1(); 
void part2(); 

file1.cpp

#include <iostream> 
#include "template.h" 
const int CLASS::global = 11; 
template <class T> 
const int TEMPLATE<T>::global = 21; 
void part1() 
{ 
    std::cout << TEMPLATE<int>::global << std::endl; 
    std::cout << CLASS::global << std::endl; 
} 

file2.cpp

#include <iostream> 
#include "template.h" 
const int CLASS::global = 21; 
template <class T> 
const int TEMPLATE<T>::global = 22; 
void part2() 
{ 
    std::cout << TEMPLATE<int>::global << std::endl; 
    std::cout << CLASS::global << std::endl; 
} 

main.cpp

#include <stdio.h> 
#include "template.h" 
void main() 
{ 
    part1(); 
    part2(); 
} 

Я принимаю, что этот пример полностью надуман, но, надеюсь, он демонстрирует, почему «изменение сильных ссылок на слабые ссылки» - это нарушение.

Скомпилирует? Нет, потому что у него есть 2 ссылки на CLASS :: global.

Если вы удалите одну из сильных ссылок на CLASS :: global, она будет скомпилирована? Да

Какова ценность TEMPLATE :: global?

Какова ценность CLASS :: global?

Недостатком является undefined, потому что это зависит от порядка ссылок, что делает его неясным в лучшем случае и в зависимости от неконтролируемого компоновщика. Это, вероятно, приемлемо, потому что редко удается сохранить все шаблоны в одном файле, потому что оба прототипа и реализация необходимы вместе для компиляции для работы.

Однако для членов статических данных класса, поскольку они были исторически значимыми ссылками и не определялись в декларации, было правилом, а теперь, по крайней мере, обычной практикой иметь полное объявление данных с сильной ссылкой в ​​файле реализации ,

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

Необходимо помнить, время внедрения C + +, 8-битных, 16-битных и 32-битных процессоров все еще были действительными целями, у AMD и Intel были одинаковые, но разные наборы инструкций, производители аппаратных средств предпочитали закрытые частные интерфейсы для открытых стандартов. И цикл сборки может занимать часы, дни, даже неделю.

+1

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

1

Структура C++ Build была довольно простой.

Компилируемые объектные файлы, которые обычно содержат одну реализацию класса. Затем компоновщик соединял все файлы объектов вместе в исполняемый файл.

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

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

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

Edit:

Назад в старые времена линкеров часто связанных объектных файлов, созданных с помощью различных языков.Общепринято связывать ASM и C, и даже после C++ часть этого кода все еще использовалась и абсолютно требовала ODR. Просто потому, что ваш проект связан только с файлами C++, это не значит, что все, что может сделать компоновщик, не изменится, потому что большинство проектов теперь являются исключительно C++. Даже сейчас многие драйверы устройств используют компоновщик в соответствии с его более оригинальным намерением.

Ответ:

Представляется, однако, что эти «сложные правила линкер» существуют и используются в случае шаблона, так почему бы не в простом случае тоже?

Компилятор управляет случаями шаблонов и просто создает слабые ссылки ссылок.

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

Таким образом, правила компоновщика не выполняются с помощью шаблонов, но правила компоновщика по-прежнему важны, поскольку ODR является требованием ASM и C, которые линкер все еще связывает, и людей, которые вы еще не используете.

+1

«компоновщик почти не имеет ничего общего с шаблонами» - это не совсем так. Компилятор объединяет повторяющиеся функции и данные, если компилятор отмечает их как слияние. Шаблонные экземпляры (функции и статические данные) настолько отмечены, почему нестатистические статические члены не являются? –

+0

@ н.м. Точно, хорошо поставленный –

+0

Какой точный вид старого кода будет сломан и каким образом? Я не вижу причин для поломки. –

Смежные вопросы