ตรวจสอบให้แน่ใจในเวลารวบรวมว่ามีการเรียกใช้เมธอดในที่เดียว


15

ฉันอยากรู้ว่าเป็นไปได้หรือไม่ที่จะมั่นใจได้ว่าในเวลารวบรวมว่ามีการเรียกใช้วิธีการในที่เดียว

โปรดทราบว่ามันก็โอเคถ้าฟังก์ชั่นถูกเรียกมากกว่าหนึ่งครั้ง (เช่นในลูป) - แต่มันไม่ควรถูกเรียกในลูปสองวงแยกกัน

สิ่งนี้สามารถแบ่งออกเป็นสองส่วนฉันยังสนใจในโซลูชันที่ครอบคลุมส่วนใดส่วนหนึ่ง:
(a) ตรวจสอบให้แน่ใจว่ามีการเรียกวิธีการอย่างน้อยหนึ่งแห่ง
(b) ตรวจสอบให้แน่ใจว่ามีการเรียกวิธีการในที่เดียว

ฉันสามารถควบคุมโครงสร้างของโค้ดได้อย่างเต็มที่และสำนวนต่าง ๆ ที่ได้รับแนวคิดเดียวกันก็ยินดีต้อนรับ

// class.h

class MyClass {
  public:
    void my_method();
}

ต่อไปนี้ไม่ควรรวบรวม (ไม่เคยเรียก)

#include "class.h"

int main() {
  MyClass my_class;
}

ข้อมูลต่อไปนี้ไม่ควรรวบรวม (เรียกมากกว่าหนึ่งแห่ง)

#include "class.h"

int main() {
  MyClass my_class;
  my_class.my_method();
  while(true) {
    my_class.my_method();
  }
}

ต่อไปนี้ควรรวบรวม (เรียกว่าในที่เดียว):

#include "class.h"

int main() {
  MyClass my_class;
  while(true) {
    my_class.my_method();
  }
}

2
อย่าทำให้เป็นวิธี วางโค้ดแบบอินไลน์ไว้ในที่เดียว
user207421

2
ฉันคิดว่าคุณสามารถทำได้ด้วยแลมบ์ดา (อาจเป็นแลมบ์ดาที่ว่างเปล่า) เพราะประเภทของการปิดนั้นเป็นเอกลักษณ์ของแลมบ์ดาแต่ละครั้ง นี่เป็นข้อผิดพลาดรันไทม์อีกครั้ง แต่นั่นไม่ใช่สิ่งที่คุณขอ หากคุณให้รายละเอียดเพิ่มเติมเกี่ยวกับปัญหาที่คุณพยายามแก้ไขเราอาจสามารถหาวิธีแก้ไขได้
Indiana Kernick

2
คุณสามารถใช้__COUNTER__มาโครที่ไม่ได้มาตรฐานเพื่อทำสิ่งนี้ static_assert(__COUNTER__ == 0); my_class.my_method();สิ่งที่ชอบ อย่างไรก็ตามตัวนับจะรีเซ็ตในหน่วยการแปลแต่ละหน่วยเพื่อให้คุณสามารถตรวจสอบได้ว่ามีการเรียกใช้ฟังก์ชันหนึ่งครั้งต่อหนึ่งหน่วยการแปล
Indiana Kernick

4
ทำไมคุณต้องการทำเช่นนั้น? ส่วนหนึ่งของจุดของฟังก์ชั่นคือมันสามารถเรียกได้จากหลายสถานที่
Chipster

4
คุณควรอธิบายว่าทำไมคุณถึงต้องการทำเช่นนี้ บางทีทางออกที่คุณขอไม่ใช่วิธีที่ดีที่สุดในการบรรลุเป้าหมายที่แท้จริง
tenfour

คำตอบ:


6

แนวทางเทคโนโลยีต่ำ:

เนื่องจากคุณมีการควบคุมโครงสร้างรหัส (ซึ่งรวมถึงระบบการสร้างฉันถือว่า) นี่เป็นโซลูชั่นที่มีเทคโนโลยีต่ำ:

  • ทำให้ชื่อฟังก์ชั่นไม่ซ้ำกันอย่างเพียงพอ
  • grep สำหรับชื่อฟังก์ชั่นในรหัสของคุณ คุณคาดหวังสองครั้ง (สมมติว่าคุณมีการประกาศและคำจำกัดความ colocated):
    • ครั้งเดียวในส่วนหัว
    • ครั้งเดียวที่ไซต์การโทรเดียว

อีกวิธีหนึ่งคือ:

หากคุณต้องการแก้ไขด้วย C ++ จริงๆคุณสามารถลองได้

  • ใช้ตัวนับเวลารวบรวมเพื่อคำนวณจำนวนการใช้ภายในหน่วยการรวบรวม
  • ตรวจสอบให้แน่ใจว่าฟังก์ชั่นจะละเมิด ODR หากส่วนหัวรวมอยู่ในหลายหน่วยการรวบรวม

อย่างไรก็ตามตัวนับเวลาการคอมไพล์เป็นเวทย์มนตร์ดำ (กล่าวว่าฉันและฉันชอบ TMP) และบังคับให้มีการละเมิด ODR สำหรับจุดประสงค์นี้ดูเหมือนว่าวูดูที่คล้ายกัน (อย่างน้อยคุณจะต้องมีกรณีทดสอบที่ไม่สามารถเชื่อมโยงได้)

แต่จริงจัง:

อย่าทำอย่างนี้ ไม่ว่าคุณจะทำอะไรมันสามารถบิดเบือนได้โดยแทบไม่ต้องใช้ความพยายามจากฟังก์ชั่น wrapper:

auto call_my_method(MyClass& o)
{
   return o.my_method();
}

MyClass::my_method()เรียกว่าเฉพาะในเสื้อคลุม ทุกคนอื่นเพียงแค่เรียกเสื้อคลุมซึ่งอาจจะถูกขีดเส้นใต้โดยคอมไพเลอร์

ตามที่คนอื่นแนะนำ: มันอาจมีประโยชน์มากกว่านี้ถ้าคุณจะอธิบายว่าคุณพยายามทำอะไร


1

นี่เป็นแนวคิดคร่าวๆที่อาจใช้งานได้ (ยาวเกินไปสำหรับความคิดเห็น แต่ไม่สมบูรณ์สำหรับคำตอบ SO ที่ดี)

คุณสามารถทำได้โดยการนับ / ตรวจสอบอินสแตนซ์ของเทมเพลต
แม่แบบ instantiated เฉพาะเมื่อใช้งาน

ในทำนองเดียวกันแม่แบบวิธีการ / ฟังก์ชั่นร่างกายจะไม่แยกวิเคราะห์หรือรวบรวมหรือเชื่อมโยง ซึ่งหมายความว่าไม่มีการสร้างอินสแตนซ์ใด ๆ ภายในร่างกายของพวกเขา)

คุณอาจสร้างเทมเพลตที่รักษาจำนวนอินสแตนซ์โกลบอลและยืนยันแบบคงที่ (หรือกลไก TMP อื่น ๆ เพื่อตรวจสอบอินสแตนซ์ที่ผ่านมา)


การนับอินสแตนซ์ "global" จะเป็นโลคัลสำหรับหน่วยการคอมไพล์ปัจจุบัน
atomsymbol

1

มีคำตอบบางส่วนสำหรับคำถามนี้โดยใช้ตัวประมวลผลล่วงหน้า C และ GNU inline assembly:

ไฟล์ส่วนหัวa.h:

struct A {
    // Do not call this method directly, use the macro below to call it
    int _method_vUcaJB5NKSD3upQ(int i, int j);
};

// Use inline assembly to ensure that this macro is used at most once
#define method_vUcaJB5NKSD3upQ(args...) \
    _method_vUcaJB5NKSD3upQ(args); \
    asm (".global once_vUcaJB5NKSD3upQ; once_vUcaJB5NKSD3upQ:");

ไฟล์การใช้งานa.cc:

#include <iostream>
#include "a.h"

int A::_method_vUcaJB5NKSD3upQ(int i, int j) { return i+j+5; }

// Ensure that the macro is used at least once
extern "C" const char once_vUcaJB5NKSD3upQ;
static const char get_vUcaJB5NKSD3upQ = once_vUcaJB5NKSD3upQ;

int main() {
    A a;
    for(int i=0; i<7; i++) {
        // Use a separate statement to call the method
        // (terminated by a semicolon, it cannot be a sub-expression)
        auto x = a.method_vUcaJB5NKSD3upQ(2, 3);
        std::cout << x << std::endl;
    }
    return 0;
}

วิธีนี้มีบางส่วนในแง่ที่ว่ามันไม่ได้ป้องกันโปรแกรมที่เรียกวิธีการเริ่มต้นด้วยขีดล่างโดยตรงโดยไม่ต้องใช้แมโคร wrapper


0

ใช้ตัวนับ constexpr มีการนำไปใช้ในคำถามอื่น


1
ดูเหมือนว่าวิธีนี้จะเกิดขึ้นไม่ดี
Chipster

ปัญหาopen-std.org/jtc1/sc22/wg21/docs/cwg_active.html#2118อ้างอิงในการตอบคำถามดังกล่าวระบุว่ามันเป็นข้อผิดพลาดของมาตรฐานและควรทำให้เกิดความผิดพลาด
SD57

อย่างน้อยก็ยังไม่เป็นรูปเป็นร่าง?
Chipster

หากยังไม่ได้จัดรูปแบบที่ไม่ดีควรใช้คนให้เร็วที่สุดเท่าที่จะทำได้เพื่อที่พวกเขาจะต้องรองรับกรณีการใช้งานนี้!
user1685095
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.