C ++:将类传递给vararg函数

时间:2013-01-21 04:26:52

标签: c++ c printf

我正在尝试创建一个类似于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)使用与上述代码相同的内容?

4 个答案:

答案 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_errorstd::logic_error

答案 2 :(得分:0)

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

关于您的问题:您的示例代码非常幸运!你有没有想过椭圆如何工作(在机器代码中)在C和为什么它不允许通过它传递非平凡的类型? 简而言之:printf()只是查看一个格式字符串,如果它看到'%s',它会认为下一个参数是char*就是全部!因此,如果您传递任何其他内容(例如charshort等),那么它将是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

免责声明:我是这个图书馆的作者