c++ と OpenGL3+ を使用したグラフィックス プログラミングに頭を悩ませようとしているときに、char 型、それへのポインター、および他の char ポインター型への潜在的な暗黙的または明示的な変換に関する、少し専門的な理解の問題に遭遇しました。解決策を見つけることができたと思いますが、これについてあなたの見解を求めて再確認したいと思います.
現在 (2014 年 10 月) のOpenGL4.5 コア プロファイル仕様(第 2.2 章のコマンド構文の表 2.2) には、OpenGL データ型がリストされ、明示的に記載されています。
GL 型は C 型ではありません。したがって、たとえば、GL 型 int は、このドキュメント以外では GLint と呼ばれ、必ずしも C 型 int と同等ではありません。実装では、表に示されている正確な数のビットを使用して GL 型を表す必要があります。
この表の GLchar 型は、文字列を構成する文字を表すために使用されるビット幅 8 の型として指定されています。
GLchar が提供するものをさらに絞り込むために、GLSL 仕様( OpenGL Shading Language 4.50、2014年 7 月、Chapter 3.1 Character Set and Phases of Compilation) を参照できます。
OpenGL シェーディング言語に使用されるソース文字セットは、UTF-8 エンコード方式の Unicode です。
今、私が探していた OpenGL ライブラリ ヘッダーでこれを実装する方法は簡単です。
typedef char GLchar;
もちろん、これは私が引用したばかりの「GL 型は C 型ではない」という声明に反するものです。
typedef は、基になる型が将来変更される可能性がある状況を想定しているため、通常、これは問題になりません。
問題はユーザー実装で始まります。
OpenGL に関するいくつかのチュートリアルを経て、GLSL ソース コードを処理に必要な GLchar 配列に割り当てるさまざまな方法に出会いました。(すべてのリンクを提供していないことをお許しください。現在、そうするために必要な評判がありません。)
サイト open.gl はこれを行うのが好きです:
const GLchar* vertexSource =
"#version 150 core\n"
"in vec2 position;"
"void main() {"
" gl_Position = vec4(position, 0.0, 1.0);"
"}";
またはこれ:
// Shader macro
#define GLSL(src) "#version 150 core\n" #src
// Vertex shader
const GLchar* vertexShaderSrc = GLSL(
in vec2 pos;
void main() {
gl_Position = vec4(pos, 0.0, 1.0);
}
);
lazyfoo.net (Chapter 30 Loading Text File Shaders) では、ソース コードがファイルから変数に読み込まれ (私の好みの方法) std::string shaderString
、GL 文字列の初期化に使用されます。
const GLchar* shaderSource = shaderString.c_str();
私がこれまでに見た中で最も冒険的なアプローチは、シェーダー ファイルを Google でロードしたときに最初に得たものです。明示的なキャストを使用する OpenGL SDK でホストされているロードに関する ClockworkCoders チュートリアルではなく、GLchar*
次GLubyte*
のようにします。
GLchar** ShaderSource;
unsigned long len;
ifstream file;
// . . .
len = getFileLength(file);
// . . .
*ShaderSource = (GLubyte*) new char[len+1];
まともな C++ コンパイラは、ここで無効な変換エラーを出します。g++ コンパイラは、-fpermissive フラグが設定されている場合にのみ、警告を出して実行します。そのようにコンパイルすると、最終的には と同じ長さの基本型のGLubyte
単なるtypedef
エイリアスであるため、コードは機能します。この場合、暗黙的なポインター変換によって警告が生成される可能性がありますが、それでも正しいことを行う必要があります。これは C++ 標準に反するため、 はorと互換性がないため、このようにするのは悪い習慣です。それは私が持っていた問題に私をもたらします: unsigned char
char
char*
signed
unsigned char*
私の要点は、これらのチュートリアルはすべて、OpenGL 仕様の実装が現在、基本的な型の typedef の形式での単なるウィンドウ ドレッシングであるという基本的な事実に依存しているということです。この仮定は、仕様ではまったくカバーされていません。さらに悪いことに、GL 型を C 型と見なすことは明らかに推奨されていません。
将来、何らかの理由で OpenGL の実装が変更され、それGLchar
が の単純なtypedef
エイリアスではなくなったchar
場合、互換性のない型へのポインタ間の暗黙的な変換が行われないため、このようなコードはコンパイルされなくなります。場合によっては、無効なポインター変換を無視するようにコンパイラーに指示することは確かに可能ですが、そのような不適切なプログラミングへのゲートを開くと、コードに他のあらゆる種類の問題が発生する可能性があります。
私の理解では、それを正しく行っている場所を 1 つだけ見たことがあります。シェーダー コンパイルに関する公式の opengl.org wiki の例です。
std::string vertexSource = //Get source code for vertex shader.
// . . .
const GLchar *source = (const GLchar *)vertexSource.c_str();
他のチュートリアルとの唯一の違いは、割り当て前の明示的なキャストです。const GLchar*
醜いことはわかっていますが、私が見る限り、OpenGL 仕様の有効な将来の実装に対してコードを安全にします (要約): UTF-8 エンコーディング スキームで文字を表すビット サイズ 8 のタイプ。
私の推論を説明するために、GLchar2
この仕様を満たす単純なクラスを作成しましたが、基本型への、または基本型からの暗黙的なポインター変換は許可されなくなりました。
// GLchar2.h - a char type of 1 byte length
#include <iostream>
#include <locale> // handle whitespaces
class GLchar2 {
char element; // value of the GLchar2 variable
public:
// default constructor
GLchar2 () {}
// user defined conversion from char to GLchar2
GLchar2 (char element) : element(element) {}
// copy constructor
GLchar2 (const GLchar2& c) : element(c.element) {}
// destructor
~GLchar2 () {}
// assignment operator
GLchar2& operator= (const GLchar2& c) {element = c; return *this;}
// user defined conversion to integral c++ type char
operator char () const {return element;}
};
// overloading the output operator to correctly handle GLchar2
// due to implicit conversion of GLchar2 to char, implementation is unnecessary
//std::ostream& operator<< (std::ostream& o, const GLchar2 character) {
// char out = character;
// return o << out;
//}
// overloading the output operator to correctly handle GLchar2*
std::ostream& operator<< (std::ostream& o, const GLchar2* output_string) {
for (const GLchar2* string_it = output_string; *string_it != '\0'; ++string_it) {
o << *string_it;
}
return o;
}
// overloading the input operator to correctly handle GLchar2
std::istream& operator>> (std::istream& i, GLchar2& input_char) {
char in;
if (i >> in) input_char = in; // this is where the magic happens
return i;
}
// overloading the input operator to correctly handle GLchar2*
std::istream& operator>> (std::istream& i, GLchar2* input_string) {
GLchar2* string_it;
int width = i.width();
std::locale loc;
while (std::isspace((char)i.peek(),loc)) i.ignore(); // ignore leading whitespaces
for (string_it = input_string; (((i.width() == 0 || --width > 0) && !std::isspace((char)i.peek(),loc)) && i >> *string_it); ++string_it);
*string_it = '\0'; // terminate with null character
i.width(0); // reset width of i
return i;
}
クラスの記述に加えて、入力および出力ストリーム演算子のオーバーロードを実装して、クラスからの読み取りと書き込み、および c-string スタイルのヌル終了GLchar2
配列を正しく処理することに注意してください。char
これは、クラスの内部構造を知らなくても可能です。ただし、型とGLchar2
(それらのポインターではない)の間の暗黙的な変換を提供する場合に限ります。char
とGLchar2
またはそれらのポインター型の間で明示的な変換は必要ありません。
この の実装GLchar
が価値がある、または完全であると主張するつもりはありませんが、デモンストレーションの目的で行う必要があります。と比較するとtypedef char GLchar1;
、このタイプでできることとできないことがわかります。
// program: test_GLchar.cpp - testing implementation of GLchar
#include <iostream>
#include <fstream>
#include <locale> // handle whitespaces
#include "GLchar2.h"
typedef char GLchar1;
int main () {
// byte size comparison
std::cout << "GLchar1 has a size of " << sizeof(GLchar1) << " byte.\n"; // 1
std::cout << "GLchar2 has a size of " << sizeof(GLchar2) << " byte.\n"; // 1
// char constructor
const GLchar1 test_char1 = 'o';
const GLchar2 test_char2 = 't';
// default constructor
GLchar2 test_char3;
// char conversion
test_char3 = '3';
// assignment operator
GLchar2 test_char4;
GLchar2 test_char5;
test_char5 = test_char4 = 65; // ASCII value 'A'
// copy constructor
GLchar2 test_char6 = test_char5;
// pointer conversion
const GLchar1* test_string1 = "test string one"; // compiles
//const GLchar1* test_string1 = (const GLchar1*)"test string one"; // compiles
//const GLchar2* test_string2 = "test string two"; // does *not* compile!
const GLchar2* test_string2 = (const GLchar2*)"test string two"; // compiles
std::cout << "A test character of type GLchar1: " << test_char1 << ".\n"; // o
std::cout << "A test character of type GLchar2: " << test_char2 << ".\n"; // t
std::cout << "A test character of type GLchar2: " << test_char3 << ".\n"; // 3
std::cout << "A test character of type GLchar2: " << test_char4 << ".\n"; // A
std::cout << "A test character of type GLchar2: " << test_char5 << ".\n"; // A
std::cout << "A test character of type GLchar2: " << test_char6 << ".\n"; // A
std::cout << "A test string of type GLchar1: " << test_string1 << ".\n";
// OUT: A test string of type GLchar1: test string one.\n
std::cout << "A test string of type GLchar2: " << test_string2 << ".\n";
// OUT: A test string of type GLchar2: test string two.\n
// input operator comparison
// test_input_file.vert has the content
// If you can read this,
// you can read this.
// (one whitespace before each line to test implementation)
GLchar1* test_string3;
GLchar2* test_string4;
GLchar1* test_string5;
GLchar2* test_string6;
// read character by character
std::ifstream test_file("test_input_file.vert");
if (test_file) {
test_file.seekg(0, test_file.end);
int length = test_file.tellg();
test_file.seekg(0, test_file.beg);
test_string3 = new GLchar1[length+1];
GLchar1* test_it = test_string3;
std::locale loc;
while (test_file >> *test_it) {
++test_it;
while (std::isspace((char)test_file.peek(),loc)) {
*test_it = test_file.peek(); // add whitespaces
test_file.ignore();
++test_it;
}
}
*test_it = '\0';
std::cout << test_string3 << "\n";
// OUT: If you can read this,\n you can read this.\n
std::cout << length << " " <<test_it - test_string3 << "\n";
// OUT: 42 41\n
delete[] test_string3;
test_file.close();
}
std::ifstream test_file2("test_input_file.vert");
if (test_file2) {
test_file2.seekg(0, test_file2.end);
int length = test_file2.tellg();
test_file2.seekg(0, test_file2.beg);
test_string4 = new GLchar2[length+1];
GLchar2* test_it = test_string4;
std::locale loc;
while (test_file2 >> *test_it) {
++test_it;
while (std::isspace((char)test_file2.peek(),loc)) {
*test_it = test_file2.peek(); // add whitespaces
test_file2.ignore();
++test_it;
}
}
*test_it = '\0';
std::cout << test_string4 << "\n";
// OUT: If you can read this,\n you can read this.\n
std::cout << length << " " << test_it - test_string4 << "\n";
// OUT: 42 41\n
delete[] test_string4;
test_file2.close();
}
// read a word (until delimiter whitespace)
test_file.open("test_input_file.vert");
if (test_file) {
test_file.seekg(0, test_file.end);
int length = test_file.tellg();
test_file.seekg(0, test_file.beg);
test_string5 = new GLchar1[length+1];
//test_file.width(2);
test_file >> test_string5;
std::cout << test_string5 << "\n";
// OUT: If\n
delete[] test_string5;
test_file.close();
}
test_file2.open("test_input_file.vert");
if (test_file2) {
test_file2.seekg(0, test_file2.end);
int length = test_file2.tellg();
test_file2.seekg(0, test_file2.beg);
test_string6 = new GLchar2[length+1];
//test_file2.width(2);
test_file2 >> test_string6;
std::cout << test_string6 << "\n";
// OUT: If\n
delete[] test_string6;
test_file2.close();
}
// read word by word
test_file.open("test_input_file.vert");
if (test_file) {
test_file.seekg(0, test_file.end);
int length = test_file.tellg();
test_file.seekg(0, test_file.beg);
test_string5 = new GLchar1[length+1];
GLchar1* test_it = test_string5;
std::locale loc;
while (test_file >> test_it) {
while (*test_it != '\0') ++test_it; // test_it points to null character
while (std::isspace((char)test_file.peek(),loc)) {
*test_it = test_file.peek(); // add whitespaces
test_file.ignore();
++test_it;
}
}
std::cout << test_string5 << "\n";
// OUT: If you can read this,\n you can read this.\n
delete[] test_string5;
test_file.close();
}
test_file2.open("test_input_file.vert");
if (test_file2) {
test_file2.seekg(0, test_file2.end);
int length = test_file2.tellg();
test_file2.seekg(0, test_file2.beg);
test_string6 = new GLchar2[length+1];
GLchar2* test_it = test_string6;
std::locale loc;
while (test_file2 >> test_it) {
while (*test_it != '\0') ++test_it; // test_it points to null character
while (std::isspace((char)test_file2.peek(), loc)) {
*test_it = test_file2.peek(); // add whitespaces
test_file2.ignore();
++test_it;
}
}
std::cout << test_string6 << "\n";
// OUT: If you can read this,\n you can read this.\n
delete[] test_string6;
test_file2.close();
}
// read whole file with std::istream::getline
test_file.open("test_input_file.vert");
if (test_file) {
test_file.seekg(0, test_file.end);
int length = test_file.tellg();
test_file.seekg(0, test_file.beg);
test_string5 = new GLchar1[length+1];
std::locale loc;
while (std::isspace((char)test_file.peek(),loc)) test_file.ignore(); // ignore leading whitespaces
test_file.getline(test_string5, length, '\0');
std::cout << test_string5 << "\n";
// OUT: If you can read this,\n you can read this.\n
delete[] test_string5;
test_file.close();
}
// no way to do this for a string of GLchar2 as far as I can see
// the getline function that returns c-strings rather than std::string is
// a member of istream and expects to return *this, so overloading is a no go
// however, this works as above:
// read whole file with std::getline
test_file.open("test_input_file.vert");
if (test_file) {
std::locale loc;
while (std::isspace((char)test_file.peek(),loc)) test_file.ignore(); // ignore leading whitespaces
std::string test_stdstring1;
std::getline(test_file, test_stdstring1, '\0');
test_string5 = (GLchar1*) test_stdstring1.c_str();
std::cout << test_string5 << "\n";
// OUT: If you can read this,\n you can read this.\n
test_file.close();
}
test_file2.open("test_input_file.vert");
if (test_file2) {
std::locale loc;
while (std::isspace((char)test_file2.peek(),loc)) test_file2.ignore(); // ignore leading whitespaces
std::string test_stdstring2;
std::getline(test_file2, test_stdstring2, '\0');
test_string6 = (GLchar2*) test_stdstring2.c_str();
std::cout << test_string6 << "\n";
// OUT: If you can read this,\n you can read this.\n
test_file.close();
}
return 0;
}
GLchar
C++ 標準に違反することなく常に文字列を正しく処理するコードを作成するには、少なくとも 2 つの実行可能な方法があると結論付けています。
char 配列から配列への明示的な変換を使用します
GLchar
(乱雑ですが実行可能です)。const GLchar* sourceCode = (const GLchar*)"some code";
std::string sourceString = std::string("some code"); // can be from a file GLchar* sourceCode = (GLchar*) sourceString.c_str();
入力ストリーム演算子を使用して、文字列をファイルから配列に直接読み取ります
GLchar
。
2 番目の方法には、明示的な変換が必要ないという利点がありますが、それを実装するには、文字列用のスペースを動的に割り当てる必要があります。もう 1 つの潜在的な欠点は、OpenGL が入力および出力ストリーム演算子の型またはポインター型を処理するためのオーバーロードを必ずしも提供しないことです。ただし、既に示したように、これらのオーバーロードを自分で作成することは、少なくとも char との間の型変換が実装されている限り、魔法の問題ではありません。
これまでのところ、c-string とまったく同じ構文を提供する、ファイルからの入力に対する実行可能なオーバーロードは他に見つかりませんでした。
さて、私の質問は次のとおりです。私のコードが OpenGL によって行われる可能性のある変更に対して安全なままであるように、これを正しく考えましたか?答えが「はい」か「いいえ」かに関係なく、上位互換性を確保するためのより良い (つまり、より安全な) 方法はありますか?私のコードの?
また、このstackoverflowの質問と回答を読みましたが、私が知る限り、文字列は基本的な型ではないため、カバーしていません。
また、暗黙的なポインター変換を提供するクラスを作成する方法も尋ねていません (ただし、それは興味深い演習になります)。この例のクラスのポイントは、実装を変更することを決定した場合に OpenGL がそのようなものを提供するという保証がないため、暗黙的なポインター割り当てを禁止することです。