0

Take this bit of a method:

int CMeetingScheduleAssistantApp::DoMessageBox(LPCTSTR lpszPrompt, UINT nType, UINT nIDPrompt)
{
    CString strContent = CString(lpszPrompt);
    CString strTitle = CString();

    if (!CTaskDialog::IsSupported())
        return CWinAppEx::DoMessageBox(lpszPrompt, nType, nIDPrompt);

    ENSURE(strTitle.LoadString(AFX_IDS_APP_TITLE));
    CTaskDialog dlgTaskMessageBox(strContent, _T(""), strTitle);

    // =================================
    // Can this be calculated just once?

    HDC screen = GetDC(nullptr);
    auto hSize = static_cast<double>(GetDeviceCaps(screen, HORZSIZE));
    auto hRes = static_cast<double>(GetDeviceCaps(screen, HORZRES));
    auto PixelsPerMM = hRes / hSize;   // pixels per millimeter
    auto MaxPixelWidth = PixelsPerMM * 150.0;
    auto PixelWidth = (hRes / 100.0) * 30.0;
    // =================================

    int iDialogUnitsWidth = MulDiv(
        min(static_cast<int>(PixelWidth), static_cast<int>(MaxPixelWidth)), 4, LOWORD(GetDialogBaseUnits()));
    dlgTaskMessageBox.SetDialogWidth(iDialogUnitsWidth);

    // Code snipped
}

Is it possible to adjust this function so that it calculates the MaxPixelWidth value only once? Without the need to add other variables to my class?

My objective is to allow multiple calls to DoMessageBox and and only calculate the max width just the once.

Andrew Truckle
  • 17,769
  • 16
  • 66
  • 164
  • Make your variable a member `m_MaxPixelWidth` of the class. Add another member `bool` variable `m_isSetMaxPixelWidth`. Add a function to your class `SetMaxPixelWidth(int width)`. And everything else you have to do is obvious. I do not see where is you difficulty here. – sergiol Feb 04 '21 at 12:23
  • @sergiol I am aware of adding member variables and reading them. I was asking if my function could be self-contained. I understand that maybe `static` variables are the way to go where they can only be defined once. But I am not sure. – Andrew Truckle Feb 04 '21 at 12:34
  • 1
    @sergiol BTW you don't really need a `bool`. If you default the width to `-1` in the constructor you will know if it has been set or not. I was just trying to avoid adding a variable to the app class for this (two widths - 30% and 150mm). No point recalculating both all the time. – Andrew Truckle Feb 04 '21 at 12:36
  • 1
    Better: why don't you calc such vars in `InitInstance` or in the constructor? I do not see any dependence of the main window dialog creation, so you can get the values before. – sergiol Feb 04 '21 at 12:38
  • 1
    @sergiol As I said, I was trying to make the function self contained. – Andrew Truckle Feb 04 '21 at 12:41
  • 1
    @sergiol I'll do it as you suggest and add a couple of member variables then and calc them in `InitInstance`. – Andrew Truckle Feb 04 '21 at 13:21
  • 1
    The time taken by the calculation is order of magnitudes smaller than the code you snipped, so it's pointless to optimize this. Unless `SetDialogWidth` is very time consuming, you tell me. Otherwise adding a bool member to the class isn't a big deal. Or you call `SetDialogWidth(0)` in the constructor and then do something like `if (GetDialogWidth() == 0) { do the calculation and call SetDialogWidth}` – Jabberwocky Feb 04 '21 at 13:43
  • @Jabberwocky All sorted. – Andrew Truckle Feb 04 '21 at 14:21
  • 1
    Sure you can do it. But that calculation is not intensive, and what happens in a multi monitor setup with different dimensions and you move it around? You can just have a local variable that is static and initialized to some value like 0 or false. If the value is the initialized value, then do the calculation and change the local variable to the uninitialized value. – Joseph Willcoxson Feb 04 '21 at 16:25
  • @JosephWillcoxson I did wonder about multiple monitor configurations. I have two but they are both the same. If I re-instate the calculations inside the method would that solve it? – Andrew Truckle Feb 04 '21 at 16:58

1 Answers1

4

That can easily be done by marking the function local value static. Static variables are initialized at most once. Since you need to initialize the variable to the result of executing code, another power feature of C++ is required: Immediately evaluated lambda expressions.

Replacing the code between the // ================================= comments with the following accomplishes what's being asked for.

static auto const MaxPixelWidth = []() {
    HDC screen = GetDC(nullptr);
    auto hSize = static_cast<double>(GetDeviceCaps(screen, HORZSIZE));
    auto hRes = static_cast<double>(GetDeviceCaps(screen, HORZRES));
    auto PixelsPerMM = hRes / hSize;   // pixels per millimeter
    ReleaseDC(screen);
    return PixelsPerMM * 150.0;
}();

This ensures that MaxPixelWidth is initialized at most once, is not accessible from outside the function, and can be made const to prevent accidental changes to it.

Live demo to illustrate the concept.


A few things to keep in mind to help you understand the consequences, and make a judicious decision on whether you want to use the above:

Using function-local static variables isn't free. If you look at Compiler Explorer's disassembly you'll observe two details:

  • The compiler allocates a hidden flag that stores whether the static has been initialized.
  • The hidden flag is updated under a lock to prevent concurrent threads from initializing the static multiple times.

Prior to any access of the static variable, the compiler emits code to check the initialization flag. Both Clang and GCC seem to use double-checked locking to avoid taking a lock (with non-trivial cost) on every access. The space and time overhead in this case is negligible. I tried understanding MSVC's code as well, but couldn't make much sense out of it. Access to the static might be more expensive by always taking a lock.

Bottom line: Using so-called magic statics isn't strictly a zero-cost abstraction. You're always paying for the flag and a thread-safe implementation, even if neither is strictly required in your use case.

IInspectable
  • 46,945
  • 8
  • 85
  • 181
  • Would this code be place inside the main function? Like an internal dynamic function? And, should I take on board the comments stated about multiple monitors. – Andrew Truckle Feb 04 '21 at 17:14
  • 1
    @and I don't know whether `GetDC` or `GetDeviceCaps` are even multi-monitor aware. Likewise, I don't know whether the return value of `GetDeviceCaps` changes when adding/removing displays, or rearranging them. Personally, I wouldn't bother optimizing this calculation, even if the concept of capturing the result of a calculation and assigning it to a static immutable variable is pretty useful. – IInspectable Feb 04 '21 at 17:24
  • I'm always stuck on the lingua fraca C++ of ancient (read MFC) C++. Thanks for the example. – Joseph Willcoxson Feb 04 '21 at 17:36
  • Side point: I have found out how to get the width of the monitor that the window will be displayed on. See: https://stackoverflow.com/questions/23492180/how-do-i-get-the-dimensions-resolution-of-each-display But I have not worked out how to get that monitors size in mm. – Andrew Truckle Feb 04 '21 at 18:08