eSe*_*mel 6 c++ opengl type-conversion type-safety char-pointer
在尝试使用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着色语言4.25,2014年7月,第3.1章字符集和编译阶段):
用于OpenGL着色语言的源字符集是UTF-8编码方案中的Unicode.
现在,我在任何OpenGL库头中实现它的方式,我关心的是一个简单的
typedef char GLchar;
Run Code Online (Sandbox Code Playgroud)
当然,我刚才引用了"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);"
"}";
Run Code Online (Sandbox Code Playgroud)
或这个:
// 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);
}
);
Run Code Online (Sandbox Code Playgroud)
在lazyfoo.net(第30章加载文本文件着色器)中,源代码从文件(我的首选方法)读取到一个std::string shaderString
变量中,然后用于初始化GL字符串:
const GLchar* shaderSource = shaderString.c_str();
Run Code Online (Sandbox Code Playgroud)
我见过的最冒险的方法是我在谷歌加载着色器文件时得到的第一个- 在OpenGL SDK上托管的ClockworkCoders教程使用显式强制转换 - 不是为了 - GLchar*
而是GLubyte*
- 像这样:
GLchar** ShaderSource;
unsigned long len;
ifstream file;
// . . .
len = getFileLength(file);
// . . .
*ShaderSource = (GLubyte*) new char[len+1];
Run Code Online (Sandbox Code Playgroud)
任何体面的c ++编译器都会在这里给出无效的转换错误.只有在设置-fpermissive标志时,g ++编译器才会发出警告.以这种方式编译它,代码将起作用,因为GLubyte
最终只是typedef
基本类型的别名,其unsigned char
长度与之相同char
.在这种情况下,隐式指针转换可能会生成警告,但仍应该做正确的事情.这违背C++标准,在那里char*
是不兼容signed
或unsigned char*
,所以做这种方式是不好的做法.这让我想到了我遇到的问题:
我的观点是,所有这些教程都依赖于这样一个基本事实,即OpenGL规范的实现目前只是基本类型的typedef形式的装饰.规范绝不涵盖此假设.更糟糕的是,明确不鼓励将GL类型视为C类型.
如果在将来的任何时候OpenGL实现应该改变 - 无论出于何种原因 - 这GLchar
不再是一个简单的typedef
别名char
,像这样的代码将不再编译,因为指向不兼容类型的指针之间没有隐式转换.虽然在某些情况下肯定可以告诉编译器忽略无效的指针转换,但打开这样的错误编程的大门可能会导致代码中出现各种其他问题.
我已经看到了一个适合我理解的地方:Shader Compilation上的官方opengl.org wiki示例,即:
std::string vertexSource = //Get source code for vertex shader.
// . . .
const GLchar *source = (const GLchar *)vertexSource.c_str();
Run Code Online (Sandbox Code Playgroud)
与其他教程的唯一区别是在作业之前进行了显式演员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;
}
Run Code Online (Sandbox Code Playgroud)
请注意,除了编写类之外,我还实现了输入和输出流操作符的重载,以正确处理类的读取和写入以及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;
}
Run Code Online (Sandbox Code Playgroud)
我的结论是,至少有两种可行的方法来编写GLchar
能够在不违反C++标准的情况下正确处理字符串的代码:
使用从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
数组.
第二种方法的优点是不需要显式转换,但为了实现它,必须动态分配字符串的空间.另一个潜在的缺点是OpenGL不一定会为输入和输出流操作符提供重载来处理它们的类型或它们的指针类型.然而,正如我已经表明的那样,只要至少实现了与char之间的类型转换,自己编写这些重载就不管巫术了.
到目前为止,我还没有发现任何其他可行的重载来自文件的输入,这些文件提供与c-strings完全相同的语法.
现在我的问题是:我是否正确地考虑了这一点,以便我的代码可以保持安全,防止OpenGL可能做出的更改 - 无论答案是肯定还是否 - 是否有更好(即更安全)的方法来确保向上兼容性我的代码?
另外,我已经阅读了这个 stackoverflow问题和答案,但据我所知,它不包括字符串,因为它们不是基本类型.
我也没有问如何编写一个提供隐式指针转换的类(虽然这将是一个有趣的练习).此示例类的要点是禁止隐式指针赋值,因为如果OpenGL决定更改其实现,则无法保证OpenGL会提供此类指针.
OpenGL规范对声明意味着什么
"GL类型不是C类型"
是的,OpenGL实现可以使用它认为适合的任何类型.这并不意味着禁止实现使用C类型.这意味着在针对OpenGL API进行编程时,不必对关于OpenGL类型的性质做出任何假设.
OpenGL指定GLchar是8位(明确未指定签名).期间,没有进一步的讨论.因此,只要您以某种方式编写程序,将GLchar视为8位数据类型,一切都很好.如果您担心有效性,可以CHAR_BIT == 8
在代码中添加静态断言,以便在平台不遵循此规则时抛出错误.
选择OpenGL标头中的typedef(标头不是标准BTW),以便生成的类型与底层平台ABI的要求相匹配.稍微更便携的gl.h可以做一个
#include <stdint.h>
typedef int8_t GLchar;
Run Code Online (Sandbox Code Playgroud)
但这只是归结为其int8_t
可能的类型定义
typedef signed char int8_t;
Run Code Online (Sandbox Code Playgroud)
对于通常的编译器.
如果在将来的任何时候OpenGL实现应该改变 - 无论出于何种原因 - 以便GLchar不再是char的简单typedef别名,像这样的代码将不再编译,因为指向不兼容类型的指针之间没有隐式转换
OpenGL没有根据C API或ABI定义.GLchar是8位,只要API绑定符合这一点,一切都很好.OpenGL规范将永远不会发生变化,GLchar
因为这不仅会对现有代码造成严重破坏,还会对GLX等OpenGL网络协议造成严重破坏.
请注意,如果您关心签名.C中签名的最重要影响是关于整数提升规则,而且在C中,许多字符操作实际上是在int
s而不是char
s上运行(使用负值作为辅助通道),并且对于整数提升规则而言char
,C 的类型是不足为奇的.签.而已.
请注意,您将很难找到平台ABI所具有的任何C实现,CHAR_BIT != 8
并且存在OpenGL实现 - 哎呀,我甚至不确定,是否存在任何C实现CHAR_BIT != 8
.不寻常的尺寸int
和short
?当然!但是char?我不知道.
关于将这整件事情纳入C++静态类型系统,我建议glstring
从std::basic_string
实例化的类型,特征和分配器派生自定义类GLchar
.说到大多数ABI中的指针类型兼容性GLchar
别名signed char
,因此表现得像标准C字符串.
扩展@datenwolf 答案:
关于CHAR_BIT
:C要求CHAR_BIT >= 8
,char
是C中最小的可寻址单元,OpenGL有8位类型。这意味着您无法在具有CHAR_BIT != 8
...的系统上实现符合要求的 OpenGL,这与声明一致
...无法在无法满足表 2.2 中精确位宽要求的架构上实现 GL API。
来自 OpenGL 4.5 规范。
根据转换GLubyte*
为char*
,据我所知,它实际上是完全有效的 C 和 C++。char*
明确允许给所有其他类型起别名,这就是为什么像这样的代码
int x;
istream &is = ...;
is.read((char*)&x, sizeof(x));
Run Code Online (Sandbox Code Playgroud)
已验证。由于sizeof(char) == sizeof(GLchar) == 1
OpenGL 和 C 位宽要求相结合,您可以GLchar
像 array of 一样自由地访问 array of char
。
您用“GL类型不是C类型”引用的段落指的是OpenGL规范使用“float”和“int”等没有“GL”前缀的类型,因此它表示尽管它使用这些无前缀的名称,它们并不(必然)引用相应的 C 类型。相反,名为“int”的 OpenGL 类型可能是具体 C 语言绑定中 C 类型“long”的别名。相反,任何健全的绑定都将使用 C 类型,以便您可以使用 OpenGL 类型编写算术表达式(在 C 中,您只能使用内置类型来做到这一点)。
我是否正确地思考了这一点,以便我的代码能够安全地抵御 OpenGL 可能做出的更改,并且 - 无论答案是是还是否 - 是否有更好(即更安全)的方法来确保我的代码的向上兼容性?
我认为您从语言律师的角度考虑了太多代码可移植性,而不是专注于学习 OpenGL 并在实践中编写可移植的代码。OpenGL 规范没有定义语言绑定,但任何 C 绑定都不会破坏每个人期望的工作方式,例如分配const GLchar *str = "hello world"
. 还要记住,这些是您通常在 C++ 中使用的C绑定,因此标头中不会出现疯狂的类和运算符重载,这实际上限制了使用表 2.2 的基本类型的实现。
编辑:
有平台与CHAR_BIT > 8
. 查看标准委员会关心的奇异架构。尽管现在它主要局限于 DSP。POSIX 要求CHAR_BIT == 8
.
不要费心实例化basic_strings
和iostreams
使用标准要求之外的类型。如果您的类型是其中之一的别名,那就没问题,但您可以直接使用前者。如果你的类型不同,你将进入一个永无休止的噩梦,其中包含无法移植解决的特征、区域设置、编解码状态等。事实上,除了char
.
归档时间: |
|
查看次数: |
859 次 |
最近记录: |