Mr.C64
Mr.C64

Reputation: 42934

Should CString's GetBufferSetLength() have a matching ReleaseBuffer() call?

According to the MSDN documentation for CString's GetBufferSetLength(), a call to that method should be followed by a matching call to ReleaseBuffer().

However, in the sample code in the same page, a comment states that calling ReleaseBuffer() is unnecessary:

CSimpleString str(pMgr);
LPTSTR pstr = str.GetBufferSetLength(3);
pstr[0] = _T('C');
pstr[1] = _T('u');
pstr[2] = _T('p');

// No need for trailing zero or call to ReleaseBuffer()
// because GetBufferSetLength() set it for us.

str += _T(" soccer is best!");
ASSERT(_tcscmp(str, _T("Cup soccer is best!")) == 0);

So, should correct code call ReleaseBuffer() after GetBufferSetLength(), or is that call unnecessary?

Upvotes: 5

Views: 1805

Answers (2)

IInspectable
IInspectable

Reputation: 51394

The documentation for CSimpleStringT::GetBufferSetLength is unambiguously clear:

If you use the pointer returned by CSimpleStringT::GetBufferSetLength to change the string contents, call ReleaseBuffer to update the internal state of CSimpleStringT before you use any other CSimpleStringT methods.

Sample code is not contractual. In the event, where sample code contradicts the formal specification, go with the formal specification. The sample code in the documentation is written against a particular implementation detail that's not part of the contract. The formal specification is contractual.


Since the documentation is a bit shallow on explaining why those class members exist at all, here is some rationale to understand the particular problem they solve, and learn why the rules are the way they are:

Classes typically have a set of properties that always hold. Those are invariants. Class member implementations are allowed to temporarily violate those invariants, but when they return, all invariants must have been restored. Client code as well as class implementations rely on those invariants.

For sake of illustration, let's assume a simplified CString implementation that stores two pieces of information: A length value and a pointer to a character array. Its invariant is that the pointer will always point to a character array of length characters followed by a NUL terminator. It is established once a class instance has been constructed, and maintained for each call to its public interface. (The actual CStringT class template stores a lot more state that needs to remain consistent.)

Occasionally, particularly when interfacing with C code, it becomes necessary to put an object into a state where invariants aren't maintained. This allows a C API to write directly into a buffer that's managed by a C++ class, reducing the overhead of allocating a temporary buffer, possibly resizing the internal buffer, and then copying everything over. That's the main use case for GetBuffer and GetBufferSetLength. Once a value has been returned, the class no longer promises to maintain any invariants. A consequence of this is that it is no longer allowed to call any members of the public interface, until the invariants have been restored. That's the purpose of ReleaseBuffer.

Mind you, there's a lot of state that needs to remain consistent. For example, CStringT maintains a capacity alongside the string length in an attempt to reduce allocations. It implements short-string optimization, and supports shared ownership with copy-on-write semantics. If you decide to be clever and skip over the mandatory call to ReleaseBuffer (like the author of the sample), all those invariants instantly become your responsibility.

A final note on whether it is or isn't allowed to call ReleaseBufferSetLength in place of ReleaseBuffer: I believe they are functionally equivalent for all values of nNewLength other than -1. The documentation is somewhat fuzzy here, calling them "functionally similar", though I think this is more of an issue with the wording rather than a hint towards an actual difference.


Note: It is not required for an implementation to fail, when used without following it's documented protocol. As a consequence, you cannot test, whether all steps of that protocol are required.

Upvotes: 3

Mark Ransom
Mark Ransom

Reputation: 308158

The purpose of ReleaseBuffer is to sync the state of the C-style string that the buffer contains with the state of the CString internal variables. Presumably this is just getting the final string length and storing it internally, and perhaps reallocating the buffer if there's a large discrepancy.

In the case of the example, the string length was stated to be exactly 3 characters. Since the size of the string didn't change via manipulation of the buffer, there's no need to update the length after that manipulation.

Upvotes: 4

Related Questions