3

出于教育目的,我在一些测试程序中使用 cstrings。我想用“...”之类的占位符来缩短字符串。

也就是说,如果我的最大长度设置为 13,"Quite a long string"将变为"Quite a lo..."。此外,我不想破坏原始字符串 - 因此缩短的字符串必须是副本。

下面的(静态)方法是我想出的。我的问题是:为我的缩短字符串分配内存的类也应该负责释放它吗? 我现在要做的是将返回的字符串存储在单独的“用户类”中,并将内存释放到该用户类。

const char* TextHelper::shortenWithPlaceholder(const char* text, size_t newSize) {
    char* shortened = new char[newSize+1];

    if (newSize <= 3) {
        strncpy_s(shortened, newSize+1, ".", newSize);
    }
    else {
        strncpy_s(shortened, newSize+1, text, newSize-3);
        strncat_s(shortened, newSize+1, "...", 3);  
    }
    return shortened;
}
4

8 回答 8

6

像这样的函数的标准方法是让用户传入一个 char[] 缓冲区。sprintf()例如,您可以在将目标缓冲区作为参数的函数中看到这一点。这允许调用者负责分配和释放内存,将整个内存管理问题放在一个地方。

于 2009-07-26T22:13:56.393 回答
5

为了避免缓冲区溢出和内存泄漏,您应该始终使用 C++ 类,例如std::string在这种情况下。

只有最后一个实例应该将类转换为低级别的东西,例如char*. 这将使您的代码简单而安全。只需将您的代码更改为:

std::string TextHelper::shortenWithPlaceholder(const std::string& text,
                                               size_t newSize) {
    return text.substr(0, newSize-3) + "...";
}

在 C 上下文中使用该函数时,您只需使用以下cstr()方法:

some_c_function(shortenWithPlaceholder("abcde", 4).c_str());

就这样!

一般来说,您不应该像在 C 中那样使用 C++ 进行编程。将 C++ 视为一种完全不同的语言更为合适。

于 2009-07-26T22:30:54.473 回答
2

我从来都不乐意将指针返回到本地分配的内存。我喜欢对任何调用我的功能进行清理的人保持健康的不信任。

相反,您是否考虑过接受将缩短的字符串复制到其中的缓冲区?

例如。

const char* TextHelper::shortenWithPlaceholder(const char* text, 
                                               size_t textSize, 
                                               char* short_text, 
                                               size_t shortSize)

其中short_text = 复制缩短字符串的缓冲区,而shortSize = 提供的缓冲区的大小。您还可以继续返回const char*指向short_text的指向,以方便调用者(如果shortSize不够大,则返回 NULL)。

于 2009-07-26T22:18:31.610 回答
2

确实,您应该只使用std::string,但如果必须,请查看现有库以获取使用指南。

在 C 标准库中,最接近您正在做的功能是

char * strncpy ( char * destination, const char * source, size_t num );

所以我会这样做:

const char* TextHelper::shortenWithPlaceholder(
    char * destination, 
    const char * source, 
    size_t newSize);

调用者负责内存管理——这允许调用者使用堆栈、堆、内存映射文件或保存该数据的任何源。您不需要记录您用于new[]分配内存的记录,调用者不需要知道使用delete[]freeor相对的delete,甚至是较低级别的操作系统调用。将内存管理留给调用者更灵活,更不容易出错。

返回一个指向目的地的指针只是一个允许你做这样的事情的好处:

char buffer[13];
printf("%s", TextHelper::shortenWithPlaceholder(buffer, source, 12));
于 2009-07-26T22:28:36.537 回答
1

最灵活的方法是返回一个包装分配内存的辅助对象,这样调用者就不必担心它了。该类存储一个指向内存的指针,并具有一个复制构造函数、一个赋值运算符和一个析构函数。

class string_wrapper
{
    char *p;

public:
    string_wrapper(char *_p) : p(_p) { }
    ~string_wrapper() { delete[] p; }

    const char *c_str() { return p; }

    // also copy ctor, assignment
};

// function declaration
string_wrapper TextHelper::shortenWithPlaceholder(const char* text, size_t newSize)
{
    // allocate string buffer 'p' somehow...

    return string_wrapper(p);
}

// caller
string_wrapper shortened = TextHelper::shortenWithPlaceholder("Something too long", 5);

std::cout << shortened.c_str();

大多数实际程序都std::string用于此目的。

于 2009-07-26T22:25:45.370 回答
0

在您的示例中,调用者别无选择,只能负责释放分配的内存。

然而,这是一个容易出错的习惯用法,我不建议使用它。

允许您使用几乎相同的代码的一种替代方法是更改shortened​​为引用的计数指针,并让方法返回引用的计数指针而不是裸指针。

于 2009-07-26T22:14:29.357 回答
0

编辑:不,我错了。我误解了你想做什么。调用者必须删除您实例中的内存。

C++ 标准规定删除 0/NULL 什么都不做(换句话说,这样做是安全的),因此无论您是否调用过该函数,都可以删除它。编辑:我不知道这是如何被遗漏的......您的另一个选择是放置删除。在这种情况下,即使它是错误的形式,您也应该使用placement new 将分配/释放保持在同一个地方(否则不一致会使调试变得荒谬)。

也就是说,您如何使用代码?我看不出你什么时候会多次调用它,但如果你这样做了,如果你不记得每个不同的内存块,就会有潜在的内存泄漏(我认为)。

我只会使用std::auto_ptror Boost::shared_ptr。它会在退出时自行删除,并且可以与 char* 一起使用。

您可以做的另一件事是考虑如何分配 TextHelper。这是一个理论上的ctor:

TextHelper(const char* input) : input_(input), copy(0) { copy = new char[sizeof(input)/sizeof(char)]; //mess with later }
~TextHelper() { delete copy; }
于 2009-07-26T22:15:44.033 回答
0

我认为有两种同样常见的基本方法:a) TextHelper 返回 c 字符串并忘记它。用户必须删除内存。b) TextHelper 维护一个已分配字符串的列表,并在销毁时释放它们。

现在这取决于您的使用模式。b) 对我来说似乎有风险:如果 TextHelper 必须解除分配字符串,则在用户完成使用缩短的字符串之前不应这样做。您可能不知道这一点何时到来,因此您要让 TextHelper 保持活动状态,直到程序终止。这导致内存使用模式等于内存泄漏。我建议 b) 仅当字符串在语义上属于提供它们的类时,类似于 std::string::c_str()。您的 TextHelper 看起来更像是一个不应与已处理字符串相关联的工具箱,因此如果我必须在两者之间进行选择,我会选择 a)。给定一个固定的 TextHelper 接口,您的用户类可能是最好的解决方案。

于 2009-07-26T22:24:24.330 回答