4

我正在尝试创建一个行为类似于 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 可变参数)来使使用与上述代码相同的东西?

4

4 回答 4

3

您可以使用强制转换触发转换运算符:

printf("Test: %s, %s\n", static_cast<const char*>(str), 
       static_cast<const char*>(global_str));

但是,我不知道您是否会遇到任何问题,避免在 C++ 代码中使用可变参数可能是最好的。

改用类型安全的 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_errorstd::logic_error虽然。

于 2013-01-21T04:38:29.857 回答
3

您几乎需要直接调用成员函数 ( 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 限定的)类型 std::nullptr_t 的参数被转换为类型 void* (4.10)。在这些转换之后,如果参数不具有算术、枚举、指针、指向成员的指针或类类型,则程序是非良构的。传递具有非平凡复制构造函数、非平凡移动构造函数或非平凡析构函数的类类型(第 9 条)的潜在评估参数,没有相应的参数,由实现定义的语义有条件地支持。

“有条件地支持实现定义的语义”为实现留下了一个开放,以通过适当的文档来支持它,但它仍然尽可能接近未定义的行为。

如果我要这样做,我想我会使用可变参数模板设置某种中介。有了这个,您将提供一个重载,(例如)在您传递类型参数时自动传递foo.c_str()给它。它(可能)是更多代码,但至少它会真正起作用。就个人而言,我会避免整件事,因为它只是麻烦多于它的价值。printfstd::string

于 2013-01-21T04:45:02.037 回答
0

你想解决什么写这个(丑陋,老实说)字符串类?为什么不使用其他东西?(like std::string) -- 在开始编写你自己的超级优化字符串之前请三思而后行...

关于您的问题:您的示例代码真的很幸运!您知道椭圆在 C 中是如何工作的(在机器代码中)以及为什么不允许通过它传递非平凡类型吗?简而言之:printf()只需查看格式字符串,如果在其中看到“%s”,则假定下一个参数仅此而已char*!因此,如果您改为传递其他任何内容(例如char,short等) - 它将是 UB!(如果与sizeof()预期不同,您很可能很快就会遇到分段错误......这就是为什么省略号在 C++ 中是一种不好的做法!它们完全是类型不安全的!

如果您使用的是 C++,请不要使用C API!有很多 C++ 库设计用于格式化输出(如 boost::format),它们是类型安全的!C++11 为类似 printf 的功能打开了大门,但有类型安全保证!只需阅读有关可变参数模板的“经典”示例......只有在阅读之后,尝试实现自己的字符串:)

于 2013-01-21T04:48:13.190 回答
0

您不能通过可变参数传递对象,只能通过指向对象的指针。但是,您可以使用(可变参数)基于模板的实现,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);
}

这将打印“测试:字符串,全局字符串!” 前提CString是正确实施。

与 Jesse Good 的实现不同,它支持标准printf格式说明符

免责声明:我是这个库的作者

于 2016-02-12T15:14:47.917 回答