新しい標準バージョンのC ++でサイレント動作の変更があったことはありますか?


104

(リストではなく、要点を証明するための例を1つか2つ探しています。)

C ++標準の変更(たとえば、98から11、11から14など)によって、既存の整形式の定義された動作のユーザーコードの動作がサイレントに変更されたことがありますか?つまり、新しい標準バージョンでコンパイルするときに警告やエラーはありませんか?

ノート:

  • 私は、実装者/コンパイラの作成者の選択ではなく、標準で義務付けられている動作について質問しています。
  • コードの工夫が少ないほど、(この質問への回答として)優れています。
  • のようなバージョン検出を備えたコードを意味するのではありません#if __cplusplus >= 201103L
  • メモリモデルに関する回答は問題ありません。

コメントは詳細な議論のためのものではありません。この会話はチャットに移動さました
SamuelLiew

3
この質問が閉じられた理由がわかりません。「新しい標準バージョンでC ++にサイレント動作の変更があったことはありますか?」は完全に焦点が当てられているようで、質問の本文はそれから逸脱していないようです。
TedLyngmo20年

私の考えでは、最大のサイレントブレイクの変更はの再定義ですauto。C ++ 11より前は、をauto x = ...;宣言しましたint。その後、それ...は何でも宣言します。
レイモンドチェン

@RaymondChen:この変更は、intを暗黙的に定義しているが、wereauto型変数を明示的に言っている場合にのみサイレントになります。私はあなたがおそらく...一方では難読化されたCコードコンテストを除き、コードのようなものを書くと、世界の人々の数を数えることができると思う
einpoklum

確かに、それが彼らがそれを選んだ理由です。しかし、それはセマンティクスの大きな変化でした。
レイモンドチェン

回答:


113

戻り値の型はC ++ 17でstring::dataからconst char*に変更さchar*れます。それは確かに違いを生む可能性があります

void func(char* data)
{
    cout << data << " is not const\n";
}

void func(const char* data)
{
    cout << data << " is const\n";
}

int main()
{
    string s = "xyz";
    func(s.data());
}

少し工夫が凝らされていますが、この合法的なプログラムは、出力をC ++ 14からC ++ 17に変更します。


7
ああ、私はstd::stringC ++ 17の変更であることにさえ気づいていませんでした。どちらかといえば、C ++ 11の変更が何らかの形でサイレント動作の変更を引き起こしたのではないかと思いました。+1。
einpoklum

9
不自然であるかどうかにかかわらず、これは整形式のコードへの変更を非常にうまく示しています。
デビッドC.ランキン

余談ですが、この変更はおそらくchar *で動作するレガシー関数を介して、std :: stringのコンテンツをその場で変更するときの面白いが正当なユースケースに基づいています。これは今では完全に正当です。ベクトルと同様に、操作できる基になる連続した配列があることが保証されています(返された参照を介していつでも可能です。現在はより自然で明示的になっています)。可能なユースケースは、編集可能な、固定長データ・セットです(いくつかの種類の例えばメッセージ)、STDに基づいている場合::コンテナ、人生の時間管理などのSTLのサービスを維持し、コピー可否など
ピーター-復活モニカ

81

答えこの質問をどのように単一使用して、ベクターの初期化を示しsize_typeた値は、C ++ 03とC ++ 11の間で異なる動作が発生することができます。

std::vector<Something> s(10);

C ++ 03デフォルト-要素タイプの一時オブジェクトを構築し、その一時オブジェクトSomethingからベクトル内の各要素をコピー構築します。

C ++ 11デフォルト-ベクトル内の各要素を構築します。

多くの場合(ほとんど?)、これらは同等の最終状態になりますが、そうしなければならない理由はありません。これは、Somethingのデフォルト/コピーコンストラクタの実装に依存します。

この不自然な例を参照してください:

class Something {
private:
    static int counter;

public:
    Something() : v(counter++) {
        std::cout << "default " << v << '\n';
    }

    Something(Something const & other) : v(counter++) {
        std::cout << "copy " << other.v << " to " << v << '\n';
    }

    ~Something() {
        std::cout << "dtor " << v << '\n';
    }

private:
    int v;
};

int Something::counter = 0;

C ++ 03は、デフォルトで1つSomethingを構築しv == 0、次にその1つからさらに10をコピー構築します。最後に、ベクトルには、v値が1から10までの10個のオブジェクトが含まれます。

C ++ 11はデフォルトで各要素を構築します。コピーは作成されません。最後に、ベクトルには、v値が0から9までの10個のオブジェクトが含まれています。


@einpoklumしかし、私は不自然な例を追加しました。:)
cdhowie

3
私はそれが不自然だとは思わない。コンストラクターが異なれば、メモリ割り当てなどの動作も異なることがよくあります。ある副作用を別の副作用(I / O)に置き換えただけです。
einpoklum

17
@cdhowieまったく考案されていません。私は最近、UUIDクラスに取り組んでいました。デフォルトのコンストラクターはランダムなUUIDを生成しました。私はこの可能性については知りませんでした。C++ 11の動作を想定しただ​​けです。
ジョン

5
これが問題となるクラスの広く使用されている実世界の例の1つは、OpenCVcv::matです。デフォルトのコンストラクターは新しいメモリーを割り当てますが、コピーコンストラクターは既存のメモリーに新しいビューを作成します。
JPA

私はそれを不自然な例とは呼びません、それは明らかに行動の違いを示しています。
デビッドウォーターワース

51

この規格には、付録C [diff]に重大な変更のリストがあります。これらの変更の多くは、サイレントな動作の変更につながる可能性があります。

例:

int f(const char*); // #1
int f(bool);        // #2

int x = f(u8"foo"); // until C++20: calls #1; since C++20: calls #2

7
@einpoklumええと、少なくとも1ダースは既存のコードの「意味を変える」または「異なる方法で実行する」と言われています。
cpplearner

4
この特定の変更の理由をどのように要約しますか?
Nayuki

4
@Nayukiは、boolバージョンを使用すること自体が意図された変更ではなく、他の変換ルールの副作用であると確信しています。本当の意図は、文字エンコーディング間の混乱を止めることです。実際の変更は、u8リテラルが以前は与えてconst char*いたが、現在は与えることconst char8_t*です。
左回り

25

彼らが新しいメソッド(そしてしばしば関数)を標準ライブラリに追加するたびに、これは起こります。

標準ライブラリタイプがあるとします。

struct example {
  void do_stuff() const;
};

ものすごく単純。一部の標準リビジョンでは、新しいメソッドまたはオーバーロード、あるいはその横に次のものが追加されています。

struct example {
  void do_stuff() const;
  void method(); // a new method
};

これにより、既存のC ++プログラムの動作を黙って変更できます。

これは、C ++の現在制限されているリフレクション機能で、そのようなメソッドが存在するかどうかを検出し、それに基づいて異なるコードを実行するのに十分であるためです。

template<class T, class=void>
struct detect_new_method : std::false_type {};

template<class T>
struct detect_new_method< T, std::void_t< decltype( &T::method ) > > : std::true_type {};

これは、新しいものを検出するための比較的簡単な方法methodであり、無数の方法があります。

void task( std::false_type ) {
  std::cout << "old code";
};
void task( std::true_type ) {
  std::cout << "new code";
};

int main() {
  task( detect_new_method<example>{} );
}

クラスからメソッドを削除するときにも同じことが起こります。

この例ではメソッドの存在を直接検出しますが、間接的に発生するこの種のことはあまり不自然ではありません。具体的な例として、反復可能かどうかに基づいてコンテナとしてシリアル化できるかどうか、またはrawバイトを指すデータとサイズメンバーがあるかどうかを判断するシリアル化エンジンがあります。もう1つ。

標準では.data()、コンテナにメソッドが追加され、タイプによって、シリアル化に使用するパスが突然変更されます。

C ++標準で実行できるのは、フリーズしたくない場合、サイレントにブレークするようなコードをまれにするか、何らかの理由で不合理にすることだけです。


3
SFINAEを除外するように質問を修飾する必要がありました。これは、私が意図したことではないためです...しかし、そうです、それは本当なので、+ 1です。
einpoklum

「この種のことは間接的に起こっている」というのは、本当の罠であるため、反対票ではなく賛成票を投じました。
イアンリングローズ

1
これは本当に良い例です。OPはそれを除外することを意図していましたが、これはおそらく、既存のコードにサイレント動作の変更を引き起こす可能性が最も高いものの1つです。1
cdhowie

1
@TedLyngmo検出器を修正できない場合は、検出されたものを変更してください。テキサスの狙撃!
Yakk-AdamNevraumont20年

15

おやおや...提供されたリンク cpplearner怖いです。

とりわけ、C ++ 20は、C ++構造体のCスタイルの構造体宣言を許可していませんでした。

typedef struct
{
  void member_foo(); // Ill-formed since C++20
} m_struct;

あなたがそのような構造体を書くことを教えられたなら(そして「クラスでC」を教える人々はまさにそれを教えます)あなたは困惑しています。


20
黒板に100回書くべきだと教えた人は誰でも「私はstructsをtypedefしません」。あなたはCでそれをするべきではありません、私見。とにかく、その変更はサイレントではありません:新しい標準では、「有効なC ++ 2017コード(匿名の非C構造体でtypedefを使用)は不正な形式である可能性があります」および「不正な形式-プログラムに構文エラーまたは診断可能なセマンティックエラーがあります。診断を発行するには、準拠するC ++コンパイラが必要です"
ピーター-モニカを復活させる

19
@ Peter-ReinstateMonicaええと、私はいつもtypedef構造体であり、チョークを無駄にすることは絶対にありません。これは間違いなく好みの問題であり、あなたの見解を共有する非常に影響力のある人々(Torvalds ...)がいますが、私のような他の人々は、型の命名規則が必要なすべてであると指摘します。コードをstructキーワードで乱雑にすると、大文字(MyClass* object = myClass_create();)が伝わらないという理解にほとんど影響しません。structあなたがあなたのコードに望むなら、私はそれを尊重します。しかし、私はそれを私の中には欲しくない。
cmaster - REINSTATEモニカ

5
とは言うものの、C ++をプログラミングするときはstruct、プレーンな古いデータ型、およびclassメンバー関数を持つものにのみ使用することをお勧めします。何がありますようしかし、あなたはCでその規則を使用することはできませんclassCで
cmaster - REINSTATEモニカ

1
@ Peter-ReinstateMonicaええ、Cで構文的にメソッドをアタッチすることはできませんが、それはCstructが実際にPODであることを意味するわけではありません。私がCコードを書く方法では、ほとんどの構造体は、単一のファイル内のコードと、それらのクラスの名前を運ぶ関数によってのみ影響を受けます。基本的には、シンタックスシュガーを含まないOOPです。これによりstruct、内部でどのような変更が行われ、どの不変条件がそのメンバー間で保証されるかを実際に制御できます。したがって、私のstructs傾向は、メンバー関数、プライベート実装、不変条件、およびデータメンバーからの抽象化です。PODのように聞こえませんか?
cmaster - REINSTATEモニカ

6
extern "C"ブロックで禁止されていない限り、この変更に問題はありません。C ++では構造体を型定義してはいけません。これは、C ++のセマンティクスがJavaとは異なるという事実ほど大きなハードルではありません。新しいプログラミング言語を学ぶとき、あなたはいくつかの新しい習慣を学ぶ必要があるかもしれません。
コーディグレイ

15

これは、C ++ 03では3を出力し、C ++ 11では0を出力する例です。

template<int I> struct X   { static int const c = 2; };
template<> struct X<0>     { typedef int c; };
template<class T> struct Y { static int const c = 3; };
static int const c = 4;
int main() { std::cout << (Y<X< 1>>::c >::c>::c) << '\n'; }

この動作の変更は、の特別な処理によって引き起こされました>>。C ++ 11より前>>は、常に右シフト演算子でした。C ++ 11では>>、テンプレート宣言の一部にすることもできます。


まあ、技術的にはこれは真実ですが、このコードは、>>その方法を使用しているため、そもそも「非公式にあいまい」でした。
einpoklum

11

トリチウムが削除されました

ソースファイルは、標準で定義されているソース文字セットに実装定義の方法でマップされる物理文字セットでエンコードされます。ソース文字セットに必要なすべての句読点がネイティブに含まれていない一部の物理文字セットからのマッピングに対応するために、言語で定義されたトリグラフ(あまり一般的でない句読文字の代わりに使用できる3つの一般的な文字のシーケンス)。これらを処理するには、プリプロセッサとコンパイラが必要でした。

C ++ 17では、トリグラフが削除されました。そのため、一部のソースファイルは、最初に物理文字セットからソース文字セットに1対1でマップされる他の物理文字セットに変換されない限り、新しいコンパイラでは受け入れられません。(実際には、ほとんどのコンパイラーはトリグラフの解釈をオプションにしました。)これは微妙な動作の変更ではありませんが、重大な変更により、以前は受け入れられていたソースファイルを外部の変換プロセスなしでコンパイルできなくなります。

より多くの制約 char

この規格は、実装で定義さている実行文字セットも参照していますが、少なくともソース文字セット全体と少数の制御コードが含まれている必要があります。

char実行文字セットのすべての値を効率的に表すことができる、符号なしの整数型として定義されたC ++標準。言語弁護士からの表現で、あなたはacharが少なくとも8ビットでなければならないと主張することができます。

実装での符号なしの値を使用している場合はchar、0〜255の範囲であることがわかっているため、考えられるすべてのバイト値を格納するのに適しています。

ただし、実装で符号付きの値を使用する場合は、オプションがあります。

ほとんどの場合、2の補数を使用しchar、最小範囲は-128〜127になります。これは256の一意の値です。

しかし、別のオプションは符号+大きさでした。この場合、1つのビットは数値が負であるかどうかを示すために予約され、他の7ビットは大きさを示します。これcharにより、-127から127の範囲が得られますが、これはわずか255の一意の値です。(-0を表すために1つの有用なビットの組み合わせが失われるためです。)

私は確かに委員会は、これまで明示的欠陥としてこれを指定されていないんだけど、あなたからの往復を保証するために、標準に頼ることができなかったので、それはだったunsigned charcharと、バックには元の値を保持します。(実際には、すべての実装は、符号付き整数型に2の補数を使用したために実行されました。)

ごく最近(C ++ 17?)、ラウンドトリップを確実にするために文言が修正されました。この修正は、に関する他のすべての要件とともに、明示的に言うことなくchar、署名の2の補数を効果的に義務付けますchar(標準では、他の符号付き整数型の符号+大きさの表現が引き続き許可されています)。すべての符号付き整数型に2の補数を使用するように要求する提案がありますが、それがC ++ 20になったのかどうかは思い出せません。

したがって、これは、以前は誤っていた 過度に大げさなコードに遡及修正を与えるため、探しているものとは逆になります。


三重音字の部分はこの質問に対する答えではありません-それは静かな変化ではありません。そして、IIANM、2番目の部分は、実装定義から厳密に義務付けられた動作への変更です。これも私が尋ねたものではありません。
einpoklum

10

これを正しいコードへの重大な変更と見なすかどうかはわかりませんが...

C ++ 11より前は、コンパイラーは、コピーコンストラクターに観察可能な副作用がある場合でも、特定の状況でコピーを削除することを許可されていましたが、必須ではありませんでした。これで、コピーの省略が保証されました。動作は基本的に、実装定義から必須になりました。

つまり、コピーコンストラクタの副作用古いバージョンで発生した可能性ありますが、新しいバージョンで発生しません。正しいコードは実装定義の結果に依存すべきではないと主張することもできますが、それはそのようなコードが正しくないと言うこととまったく同じではないと思います。


1
この「要件」はC ++ 11ではなくC ++ 17で追加されたと思いましたか?(一時的な具体化を参照してください。)
cdhowie20年

@cdhowie:あなたは正しいと思います。これを書いたとき、私は手元に標準を持っていなかったし、おそらく私の検索結果のいくつかにあまりにも多くの信頼を置いていた。
エイドリアンマッカーシー

実装定義の動作への変更は、この質問への回答としてカウントされません。
einpoklum

7

c ++ 11以降、ストリームから(数値)データを読み取り、読み取りに失敗した場合の動作が変更されました。

たとえば、ストリームから整数を読み取りますが、整数は含まれていません。

#include <iostream>
#include <sstream>

int main(int, char **) 
{
    int a = 12345;
    std::string s = "abcd";         // not an integer, so will fail
    std::stringstream ss(s);
    ss >> a;
    std::cout << "fail = " << ss.fail() << " a = " << a << std::endl;        // since c++11: a == 0, before a still 12345 
}

c ++ 11は、失敗したときに読み取り整数を0に設定するためです。c ++ <11では、整数は変更されませんでした。そうは言っても、gccは、標準を強制的にc ++ 98(-std = c ++ 98を使用)に戻した場合でも、少なくともバージョン4.4.7以降は常に新しい動作を示します。

(古い動作の方が実際には優れていました。何も読み取れないのに、値を0に変更すると、それ自体が有効になりますか?)

参照:https//en.cppreference.com/w/cpp/locale/num_get/getを参照してください


ただし、returnTypeについては変更はありません。C ++ 11以降に利用可能なニュースオーバーロードは2つだけ
ビルドは

これはC ++ 98とC ++ 11の両方で定義された動作でしたか?または、動作が定義されましたか?
einpoklum

cppreference.comが正しい場合:「エラーが発生した場合、vは変更されません。(C ++ 11まで)」したがって、動作はC ++ 11より前に定義され、変更されました。
DanRechtsaf

私の理解では、ss> aの動作は確かに定義されていますが、初期化されていない変数を読み取る非常に一般的なケースでは、c ++ 11の動作は初期化されていない変数を使用します。これは未定義の動作です。したがって、障害のデフォルト構築は、非常に一般的な未定義の動作を防ぎます。
Rasmus DamgaardNielsen20年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.