Satır içi değişkenler nasıl çalışır?


124

2016 Oulu ISO C ++ Standartları toplantısında, standartlar komitesi tarafından Satır İçi Değişkenler adlı bir teklif C ++ 17'ye oylandı.

Layman'ın terimleriyle, satır içi değişkenler nelerdir, nasıl çalışırlar ve ne için faydalıdırlar? Satır içi değişkenler nasıl bildirilmeli, tanımlanmalı ve kullanılmalıdır?


@jotik Sanırım eşdeğer işlem, değişkenin herhangi bir oluşumunu değeriyle değiştiriyor. Normalde bu yalnızca değişken ise geçerlidir const.
melpomene

5
inlineAnahtar kelimenin işlevler için yaptığı tek şey bu değildir . inlineAnahtar kelime, fonksiyonlar uygulandığında, değişkenlere doğrudan çevirir bir diğer önemli etkisi vardır. inlineMuhtemelen bir başlık dosyasında bildirilen bir işlev, başlık #includebirden çok çeviri birimi tarafından d alsa bile bağlantı zamanında "yinelenen sembol" hatalarına neden olmaz . inlineAnahtar kelime, değişkenlere uygulandığında, aynı kesin bir sonuç olacaktır. Son.
Sam Varshavchik

4
^ 'Bu işleve yapılan herhangi bir çağrıyı kendi kodunun yerinde bir kopyasıyla ikame etme' anlamında inline, iyileştirici için yalnızca zayıf, bağlayıcı olmayan bir istektir. Derleyiciler, istenen işlevleri satır içi yapmamakta ve / veya sizin açıklama eklemediklerinizi satır içi yapmakta özgürdür. Bunun yerine, inlineanahtar kelimenin asıl amacı, çoklu tanım hatalarını aşmaktır.
underscore_d

Yanıtlar:


121

Teklifin ilk cümlesi:

inline Belirteci değişkenlere yanı sıra işlevlerine uygulanabilir.

Bir işleve inlineuygulandığı şekliyle 'garantili etki , işlevin birden çok çeviri biriminde dış bağlantıyla aynı şekilde tanımlanmasına izin vermektir. Uygulamada bu, birden çok çeviri birimine dahil edilebilen bir başlıkta işlevin tanımlanması anlamına gelir. Öneri, bu olasılığı değişkenlere kadar genişletiyor.

Bu nedenle, pratik terimlerle (artık kabul edilen) teklif, bir başlık dosyasında inlineharici bir bağlantı constad alanı kapsam değişkenini veya herhangi bir staticsınıf veri üyesini tanımlamak için anahtar kelimeyi kullanmanıza izin verir , böylece bu başlık dahil edildiğinde ortaya çıkan çoklu tanımlamalar birden çeviri birimleri bağlayıcı ile Tamam - sadece seçtiği bir tanesi.

staticSınıf şablonlarındaki değişkenleri desteklemek için, C ++ 14'e kadar ve buna dahil olana kadar, bunun için dahili makine vardı , ancak bu makineyi kullanmanın uygun bir yolu yoktu. Biri gibi numaralara başvurmak gerekiyordu

template< class Dummy >
struct Kath_
{
    static std::string const hi;
};

template< class Dummy >
std::string const Kath_<Dummy>::hi = "Zzzzz...";

using Kath = Kath_<void>;    // Allows you to write `Kath::hi`.

C ++ 17'den itibaren, birinin sadece

struct Kath
{
    static std::string const hi;
};

inline std::string const Kath::hi = "Zzzzz...";    // Simpler!

… Bir başlık dosyasında.

Teklif, ifadeleri içerir

Bir hizalı statik bir veri elemanı sınıf tanımında tanımlanabilir ve aşağıdakileri s pecify bir bağ ya da-eşit-başlatıcısı. Üye constexprbelirtici ile bildirilmişse, başlatıcı olmadan ad alanı kapsamında yeniden bildirilebilir (bu kullanım artık kaldırılmıştır; bkz. DX). Diğer statik veri üyelerinin bildirimleri, bir kaşlı ayraç veya eşitleyici belirtmeyecektir.

… Bu, yukarıdakilerin daha da basitleştirilerek yalnızca

struct Kath
{
    static inline std::string const hi = "Zzzzz...";    // Simplest!
};

… TC tarafından bu cevaba yapılan bir yorumda belirtildiği gibi .

Ayrıca  ​constexprbelirteç,  inline statik veri üyelerini ve ayrıca işlevleri ifade eder.


Notlar:
¹ Bir fonksiyonun inlineoptimizasyonla ilgili ipucu etkisi vardır, derleyicinin bu fonksiyonun çağrılarını fonksiyonun makine kodunun doğrudan ikamesi ile değiştirmeyi tercih etmesi gerekir. Bu ipucu göz ardı edilebilir.


2
Ayrıca, const kısıtlaması yalnızca ad alanı kapsam değişkenleri için geçerlidir. Sınıf kapsamları (gibi Kath::hi) sabit olmak zorunda değildir.
TC

4
Daha yeni raporlar , constkısıtlamanın tamamen kaldırıldığını gösteriyor.
TC

2
@Nick: Richard Smith (şu anki C ++ komitesi "proje editörü") iki yazardan biri olduğundan ve "Clang C ++ arayüzünün kod sahibi" olduğundan Clang tahmin etti. Ve yapı Godbolt'ta clang 3.9.0 ile derlendi . Satır içi değişkenlerin bir C ++ 1z uzantısı olduğu konusunda uyarır. Kaynak ve derleyici seçimini ve seçeneklerini paylaşmanın bir yolunu bulamadım, bu yüzden bağlantı genel olarak siteye yönelik, üzgünüm.
Şerefe ve hth. - Alf

1
neden sınıf / yapı bildirimi içinde satır içi anahtar kelimeye ihtiyaç var? Neden basitçe izin vermiyorsunuz static std::string const hi = "Zzzzz...";?
sasha.sochka

2
@EmilianCioca: Hayır, statik başlatma siparişi fiyaskosuna ters düşersiniz . Bir singleton aslında bundan kaçınmak için bir cihazdır.
Şerefe ve hth. - Alf

15

Satır içi değişkenler, satır içi işlevlere çok benzer. Değişken birden çok derleme biriminde görülse bile, bağlayıcıya değişkenin yalnızca bir örneğinin olması gerektiğini bildirir. Bağlayıcının başka kopya oluşturulmadığından emin olması gerekir.

Satır içi değişkenler, yalnızca başlık kitaplıklarında küreselleri tanımlamak için kullanılabilir. C ++ 17'den önce geçici çözümler (satır içi işlevler veya şablon kesmeleri) kullanmak zorundaydılar.

Örneğin, bir geçici çözüm, Meyer'in singletonunu bir satır içi işlevle kullanmaktır:

inline T& instance()
{
  static T global;
  return global;
}

Bu yaklaşımın, çoğunlukla performans açısından bazı dezavantajları vardır. Bu ek yük, şablon çözümleriyle önlenebilir, ancak bunları yanlış anlamak kolaydır.

Satır içi değişkenlerle, bunu doğrudan bildirebilirsiniz (çoklu tanım bağlayıcı hatası almadan):

inline T global;

Yalnızca başlık kitaplıklarının yanı sıra, satır içi değişkenlerin yardımcı olabileceği başka durumlar da vardır. Nir Friedman, CppCon'daki konuşmasında bu konuyu ele alıyor: C ++ geliştiricilerinin küreseller (ve bağlayıcı) hakkında bilmesi gerekenler . Satır içi değişkenler ve geçici çözümler hakkındaki bölüm 18m9 saniyede başlar .

Uzun lafın kısası, derleme birimleri arasında paylaşılan global değişkenleri bildirmeniz gerekiyorsa, bunları başlık dosyasında satır içi değişkenler olarak bildirmek basittir ve C ++ 17 öncesi geçici çözümlerle ilgili sorunları önler.

(Örneğin, açıkça tembel başlatmaya sahip olmak istiyorsanız, Meyer'in singletonu için hala kullanım durumları vardır.)


11

Minimum çalıştırılabilir örnek

Bu harika C ++ 17 özelliği şunları yapmamızı sağlar:

  • her bir sabit için sadece tek bir bellek adresini rahatlıkla kullanın
  • olarak saklayın constexpr: constexpr extern nasıl beyan edilir?
  • bunu bir başlıktan tek bir satırda yapın

main.cpp

#include <cassert>

#include "notmain.hpp"

int main() {
    // Both files see the same memory address.
    assert(&notmain_i == notmain_func());
    assert(notmain_i == 42);
}

notmain.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

inline constexpr int notmain_i = 42;

const int* notmain_func();

#endif

notmain.cpp

#include "notmain.hpp"

const int* notmain_func() {
    return &notmain_i;
}

Derleyin ve çalıştırın:

g++ -c -o notmain.o -std=c++17 -Wall -Wextra -pedantic notmain.cpp
g++ -c -o main.o -std=c++17 -Wall -Wextra -pedantic main.cpp
g++ -o main -std=c++17 -Wall -Wextra -pedantic main.o notmain.o
./main

GitHub yukarı akış .

Ayrıca bkz: Satır içi değişkenler nasıl çalışır?

Satır içi değişkenlerde C ++ standardı

C ++ standardı, adreslerin aynı olacağını garanti eder. C ++ 17 N4659 standart taslağı 10.1.6 "Satır içi belirtici":

6 Dış bağlantılı bir satır içi işlev veya değişken, tüm çeviri birimlerinde aynı adrese sahip olmalıdır.

cppreference https://en.cppreference.com/w/cpp/language/inline , staticverilmezse, harici bağlantıya sahip olduğunu açıklar .

GCC satır içi değişken uygulaması

Nasıl uygulandığını şu şekilde gözlemleyebiliriz:

nm main.o notmain.o

içerenler:

main.o:
                 U _GLOBAL_OFFSET_TABLE_
                 U _Z12notmain_funcv
0000000000000028 r _ZZ4mainE19__PRETTY_FUNCTION__
                 U __assert_fail
0000000000000000 T main
0000000000000000 u notmain_i

notmain.o:
0000000000000000 T _Z12notmain_funcv
0000000000000000 u notmain_i

ve man nmşöyle diyor u:

"u" Sembol, benzersiz bir küresel semboldür. Bu, standart ELF simge bağlamaları kümesinin bir GNU uzantısıdır. Böyle bir sembol için dinamik bağlayıcı, tüm süreçte bu isim ve türe sahip tek bir sembolün kullanımda olduğundan emin olacaktır.

bu yüzden bunun için özel bir ELF uzantısı olduğunu görüyoruz.

Ön-C ++ 17: extern const

C ++ 17'den önce ve C'de, bir ile çok benzer bir etki elde edebiliriz extern const, bu da tek bir bellek konumunun kullanılmasına yol açar.

Olumsuz yönler inline:

  • değişkeni constexprbu teknikle yapmak mümkün değildir , sadece şunainline izin verir: constexpr extern nasıl bildirilir?
  • değişkeni başlık ve cpp dosyasında ayrı ayrı bildirmeniz ve tanımlamanız gerektiğinden daha az zariftir

main.cpp

#include <cassert>

#include "notmain.hpp"

int main() {
    // Both files see the same memory address.
    assert(&notmain_i == notmain_func());
    assert(notmain_i == 42);
}

notmain.cpp

#include "notmain.hpp"

const int notmain_i = 42;

const int* notmain_func() {
    return &notmain_i;
}

notmain.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

extern const int notmain_i;

const int* notmain_func();

#endif

GitHub yukarı akış .

Pre-C ++ 17 yalnızca başlık alternatifleri

Bunlar externçözüm kadar iyi değil , ancak işe yarıyorlar ve yalnızca tek bir bellek konumunu kaplıyorlar:

Bir constexprişlev, çünkü tanımın her çeviri biriminde görünmesini constexprima ederinline ve buna inline izin verir (zorlar) :

constexpr int shared_inline_constexpr() { return 42; }

ve eminim ki, iyi bir derleyici çağrıyı sıraya dizer.

Aşağıdaki gibi bir constveya constexprstatik tamsayı değişkeni de kullanabilirsiniz :

#include <iostream>

struct MyClass {
    static constexpr int i = 42;
};

int main() {
    std::cout << MyClass::i << std::endl;
    // undefined reference to `MyClass::i'
    //std::cout << &MyClass::i << std::endl;
}

ancak adresini almak gibi şeyler yapamazsınız, aksi takdirde çok kullanılır hale gelir, ayrıca bakınız: https://en.cppreference.com/w/cpp/language/static "Sabit statik üyeler" ve constexpr statik verilerini tanımlama üyeler

C

C'de durum C ++ ön C ++ 17 ile aynıdır, şuraya bir örnek yükledim: C'de "statik" ne anlama geliyor?

Tek fark, C ++ ' constda staticküreselleri ifade etmesidir, ancak C: C ++' statik sabit 've' sabit 'anlambilimlerinde yoktur.

Tamamen satır içi yapmanın bir yolu var mı?

TODO: Herhangi bir bellek kullanmadan değişkeni tam olarak satır içi yapmanın bir yolu var mı?

Önişlemcinin yaptığı gibi.

Bu bir şekilde şunları gerektirir:

  • değişkenin adresi alınmışsa yasaklamak veya tespit etmek
  • bu bilgiyi ELF nesne dosyalarına ekleyin ve LTO'nun bunu optimize etmesine izin verin

İlişkili:

Ubuntu 18.10, GCC 8.2.0'da test edilmiştir.


2
inlinekelimenin kendisine rağmen, satır içi ile neredeyse hiçbir ilgisi yoktur, ne fonksiyonlar ne de değişkenler için. inlinederleyiciye herhangi bir şeyi satır içi yapmasını söylemez. Bağlayıcıya, geleneksel olarak programcının işi olan tek bir tanımın olduğundan emin olmasını söyler. Yani "Tamamen satır içi yapmanın bir yolu var mı?" en azından tamamen alakasız bir sorudur.
kullanıcı değil
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.