我正在尝试创建一个类似于MS CString的类(也就是说,它将它传递给printf,它就像一个指向C字符串的指针,没有像“.c_str()”这样的额外丑陋的黑魔法。
这是这个类的第一个实现,只是有效并且还没有提供任何有用的东西:
#include <cstdlib>
#include <cstring>
class CString
{
protected:
struct CStringInfo
{
size_t Length;
size_t MaxLength;
};
public:
CString()
{
Buffer = NULL;
Assign(NULL);
}
CString(const char* chv)
{
Buffer = NULL;
Assign(chv, 0);
}
~CString()
{
if(Buffer) delete[] Buffer;
Buffer = NULL;
}
size_t GetLength()
{
if(!Buffer) Alloc(1);
return GetInfo()->Length;
}
size_t Resize(size_t size)
{
Alloc(size + 1); // + 0x00
Buffer[size] = 0;
return size;
}
bool Assign(const char* value, size_t size = 0)
{
size_t strl = ((size) ? size : strlen(value));
if(!value || !(strl = strlen(value)))
{
if(!Buffer) Alloc(1);
return false;
}
Alloc(strl + 1);
memcpy(Buffer, value, strl);
Buffer[strl] = 0;
return true;
}
CString& operator = (const char* what)
{
Assign(what);
return (*this);
}
CString& operator = (CString& string)
{
Assign(string.Buffer);
return (*this);
}
operator const char* ()
{
return Buffer;
}
protected:
char* Buffer;
void Alloc(size_t size)
{
if(!size) size = 1;
char* nb = new char[size + sizeof(CStringInfo)];
char* nbb = nb + sizeof(CStringInfo);
size_t cl = size - 1;
if(Buffer)
{
if(cl > GetInfo()->Length) cl = GetInfo()->Length;
if(cl) memcpy(nbb, Buffer, cl - 1);
nbb[cl] = 0;
*(CStringInfo*)(nb) = *(CStringInfo*)(Buffer);
delete[] (Buffer - sizeof(CStringInfo));
}
Buffer = nb;
GetInfo()->MaxLength = size;
GetInfo()->Length = cl;
}
void Free()
{
if(Buffer)
{
delete[] (Buffer - sizeof(CStringInfo));
}
}
CStringInfo* GetInfo()
{
return (CStringInfo*)(this->Buffer - sizeof(CStringInfo));
}
};
代码我测试了它:
#include <cstdio>
#include "CString.hpp"
CString global_str = "global string!";
int main(int argc, char* argv[])
{
CString str = "string";
printf("Test: %s, %s\n", str, global_str);
return 0;
}
如果我在类中没有析构函数,那么我可以将它传递给printf,它将像它应该的那样工作(作为C字符串)。但是当我添加析构函数时,GCC会产生以下错误:
error: cannot pass objects of non-trivially-copyable type 'class CString' through '...'
此外,GCC的先前版本将发出警告+ ud2操作码。
所以...问题:我是否可以在GCC中实际进行以下建设工作,或者是否有任何方式(可能不涉及C varargs)使用与上述代码相同的内容?
答案 0 :(得分:2)
您几乎需要直接调用成员函数(foo.c_str()
)或通过强制转换((char *)foo
)来调用成员函数。
否则,它取决于编译器。在C ++ 03中,行为未定义(§5.2.2/ 7):
当给定参数没有参数时,参数的传递方式是接收函数可以通过调用va_arg(18.7)来获取参数的值。在参数表达式上执行左值到右值(4.1),数组到指针(4.2)和函数到指针(4.3)标准转换。在这些转换之后,如果参数没有算术,枚举,指针,成员指针或类类型,则程序格式错误。如果参数具有非POD类类型(第9节),则行为未定义。
...但在(C ++ 11,§5.2.2/ 7)中,它得到了有条件的支持:
当给定参数没有参数时,参数的传递方式是接收左值到右值(4.1),数组到指针(4.2)和函数到指针( 4.3)对参数表达式执行标准转换。具有(可能是cv-qualified)类型std :: nullptr_t的参数将转换为void *(4.10)类型。在这些转换之后,如果参数没有算术,枚举,指针,成员指针或类类型,则程序格式错误。传递具有非平凡复制构造函数,非平凡移动构造函数或非平凡析构函数的类类型(第9章)的潜在评估参数,没有相应的参数,通过实现定义的语义有条件地支持。
“有条件地支持实现定义的语义”为实现提供了一个开放,以便用适当的文档支持它,但它仍然与你可以获得的未定义行为非常接近。
如果我打算这样做,我想我会使用可变参数模板设置某种中介。这样,当您传递类型为foo.c_str()
的参数时,您将提供一个重载(例如)自动将printf
传递给std::string
。这可能是(可能)更多的代码,但至少它实际上是有效的。就个人而言,我会避免整件事情,因为它只是比它的价值更麻烦。
答案 1 :(得分:1)
您可以使用强制转换触发转化运算符:
printf("Test: %s, %s\n", static_cast<const char*>(str),
static_cast<const char*>(global_str));
但是,我不知道你是否会遇到任何问题,避免使用C ++代码中的varargs可能是最好的。
如何使用type-safe printf代替(信用:维基百科):
void printf(const char *s)
{
while (*s) {
if (*s == '%') {
if (*(s + 1) == '%') {
++s;
}
else {
throw std::runtime_error("invalid format string: missing arguments");
}
}
std::cout << *s++;
}
}
template<typename T, typename... Args>
void printf(const char *s, T value, Args... args)
{
while (*s) {
if (*s == '%') {
if (*(s + 1) == '%') {
++s;
}
else {
std::cout << value;
printf(s + 1, args...); // call even when *s == 0 to detect extra arguments
return;
}
}
std::cout << *s++;
}
throw std::logic_error("extra arguments provided to printf");
}
我认为libstdc ++不支持std::runtime_error
和std::logic_error
。
答案 2 :(得分:0)
你想要解决这个问题(丑陋,说实话)字符串类?
为什么不使用其他? (比如std::string
) - 在开始编写你自己的超级优化字符串之前请三思而后行......
关于您的问题:您的示例代码非常幸运!你有没有想过椭圆如何工作(在机器代码中)在C和为什么它不允许通过它传递非平凡的类型?
简而言之:printf()
只是查看一个格式字符串,如果它看到'%s',它会认为下一个参数是char*
就是全部!因此,如果您传递任何其他内容(例如char
,short
等),那么它将是UB! (如果sizeof()
与预期不同,很可能很快就会出现分段错误...这就是为什么省略号在C ++中是一个糟糕的做法!它们完全是键入不安全!
如果您使用的是C ++,就是不要使用 C API!有很多C ++库设计用于格式化输出(如boost :: format),它们是类型安全的! C ++ 11为类似printf的功能打开了大门,但具有类型安全的保证!只是阅读关于可变参数模板的“经典”示例......并且只有在阅读之后,尝试实现自己的字符串:)
答案 3 :(得分:0)
您不能通过varargs传递对象,只能指向对象。但是,您可以使用printf
的基于模板的(可变量)模板实现,例如C++ Format提供的实现:
#include "format.h"
#include "CString.hpp"
CString global_str = "global string!";
std::ostream &operator<<(std::ostream &os, const CString &s) {
return os << static_cast<const char*>(s);
}
int main() {
CString str = "string";
fmt::printf("Test: %s, %s\n", str, global_str);
}
这将打印“Test:string,global string!”只要CString
正确实施。
与Jesse Good的实施不同,这支持标准printf
format specifiers。
免责声明:我是这个图书馆的作者