Рассмотрим следующую программу с двумя единицами компиляции.
// a.hpp
class A {
static const char * get() { return "foo"; }
};
void f();
// a.cpp
#include "a.hpp"
#include <iostream>
void f() {
std::cout << A::get() << std::endl;
}
// main.cpp
#include "a.hpp"
#include <iostream>
void g() {
std::cout << A::get() << std::endl;
}
int main() {
f();
g();
}
Постепенно необходимо создавать глобальные строковые константы по той или иной причине. Выполнение этого полностью наивным образом вызывает проблемы с компоновщиками. Обычно люди помещают объявление в заголовок и определение в единый блок компиляции или используют макросы.
У меня создалось впечатление, что этот способ сделать это (показанный выше) с помощью функции "хорошо", потому что это функция inline
, и компоновщик исключает любые дубликаты копий, которые создаются, и программы, написанные с использованием этот образец работает нормально. Однако теперь у меня есть сомнения относительно того, действительно ли это законно.
Функция A::get
используется как odr в двух разных единицах перевода, но она неявно встроена, поскольку она является членом класса.
В [basic.def.odr.6]
говорится:
Может быть более одного определения... встроенной функции с внешняя связь (7.1.2)... в программе, при условии, что каждое определение появляется в другой единицы перевода и при условии, что определения удовлетворяют следующим требованиям. Данный такой объект с именем
D
, определенный в более чем одной единицы перевода, а затем - каждое определениеD
должно состоять из одной и той же последовательности токенов; и
- в каждом определенииD
соответствующие имена, просмотренные в соответствии с 3.4, должны ссылаться на сущность, определенную в определенииD
или ссылаться на один и тот же объект после разрешения перегрузки (13.3) и после сопоставление частичной специализации шаблона (14.8.3), за исключением того, что имя может ссылаться на энергонезависимый const с внутренней или никакой привязкой, если объект имеет один и тот же тип литерала во всех определенияхD
, и объект инициализируется константным выражением (5.19), и объект не используется в качестве odr, а объект имеет то же значение во всех определенияхD
; и
- в каждом определенииD
соответствующие субъекты должны иметь одну и ту же языковую связь; и
-... (больше условий, которые не кажутся релевантными)Если определения
D
удовлетворяют всем этим требованиям, то программа должна вести себя так, как если бы было одно определениеD
. Если определенияD
не удовлетворяют эти требования, то поведение undefined.
В моей примерной программе два определения (по одному в каждой единицы перевода) соответствуют одной и той же последовательности токенов. (Вот почему я изначально думал, что все в порядке.)
Однако неясно, выполняется ли второе условие. Поскольку имя "foo"
может не соответствовать одному и тому же объекту в двух единицах компиляции - это потенциально "различный" строковый литерал в каждом, no?
Я попытался изменить программу:
static const void * get() { return static_cast<const void*>("foo"); }
чтобы он печатал адрес строкового литерала, и я получаю тот же адрес, однако я не уверен, что это гарантировано.
Подходит ли он к "... должен относиться к сущности, определенной в определении D
"? Рассматривается ли "foo"
в A::get
здесь? Возможно, это так, но, как я понимаю неофициально, строковые литералы в конечном итоге заставляют компилятор испускать какой-то глобальный const char[]
, который живет в специальном сегменте исполняемого файла. Является ли этот "объект" считающимся находящимся в пределах A::get
или это не имеет значения?
Является ли "foo"
даже считанным "именем", или же термин "имя" ссылается только на допустимый идентификатор С++, например, может использоваться для переменной или функции? С одной стороны, он говорит:
[basic][3.4]
Имя - это использование идентификатора (2.11), operator-function-id (13.5), literal-operator-id (13.5.8), преобразование- function-id (12.3.2) или template-id (14.2), который обозначает объект или метку (6.6.4, 6.1).
а идентификатор
[lex.name][2.11]
Идентификатор представляет собой произвольно длинную последовательность букв и цифр.
поэтому кажется, что строковый литерал не является именем.
С другой стороны, в разделе 5
[expr.prim.general][5.1.1.1]
Строковый литерал - это значение lvalue; все остальные литералы являются prvalues.
Как правило, я думал, что lvalues
имеют имена.